From d8cc3876fa3191517d8998dc68fb5667fcba7f6a Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 27 Oct 2026 19:12:46 +0100 Subject: [PATCH 01/18] feat(api): bootstrap fail-closed cross-component contract verification --- .github/workflows/api-contracts.yml | 56 + .github/workflows/marreq-ci.yml | 3 + .github/workflows/release-artifacts.yml | 57 + .gitignore | 4 + Cargo.lock | 1 + docker/Dockerfile | 2 + docker/frontend/Dockerfile | 2 + docs/developer/api-contract-routes.json | 1958 +++++ .../developer/api-contract-source-review.json | 378 + docs/developer/http-api-contract.md | 177 + docs/developer/mcp-setup.md | 12 + docs/developer/openapi.yaml | 7079 ++++++++++++++++- docs/developer/use-cases-api.md | 11 + docs/releases.md | 128 + frontend/src/api/generated-contract.ts | 450 ++ frontend/src/api/types.ts | 120 +- .../src/hooks/__tests__/useBuildInfo.test.ts | 2 +- .../src/pages/__tests__/AccountPage.test.tsx | 1 + .../src/pages/__tests__/AdminPage.test.tsx | 2 + .../src/pages/__tests__/HelpPage.test.tsx | 2 +- .../src/pages/__tests__/LoginPage.test.tsx | 2 + marreq-cloud/Cargo.toml | 1 + marreq-cloud/tests/api_contract_inventory.rs | 56 + ...ed_oauth_mcp_tools_postgres_integration.rs | 63 +- mcp-server/Dockerfile | 2 + mcp-server/compatibility.json | 4 + mcp-server/src/client.ts | 27 +- mcp-server/src/generated-contract.ts | 450 ++ mcp-server/src/remote.ts | 9 +- scripts/check-api-compatibility.sh | 3 + scripts/contracts/bundle.py | 59 + scripts/contracts/check.py | 80 + scripts/contracts/check_baseline.py | 26 + scripts/contracts/compatibility.py | 294 + scripts/contracts/generate.py | 84 + scripts/contracts/inventory.py | 26 + scripts/contracts/requirements.txt | 3 + scripts/contracts/smoke-consumers.mjs | 49 + scripts/contracts/test-backend.sh | 66 + scripts/contracts/test_compatibility.py | 227 + scripts/contracts/test_contract_tooling.py | 70 + scripts/contracts/verify_transcript.py | 90 + 42 files changed, 11850 insertions(+), 286 deletions(-) create mode 100644 .github/workflows/api-contracts.yml create mode 100644 docs/developer/api-contract-routes.json create mode 100644 docs/developer/api-contract-source-review.json create mode 100644 frontend/src/api/generated-contract.ts create mode 100644 marreq-cloud/tests/api_contract_inventory.rs create mode 100644 mcp-server/compatibility.json create mode 100644 mcp-server/src/generated-contract.ts create mode 100755 scripts/check-api-compatibility.sh create mode 100644 scripts/contracts/bundle.py create mode 100644 scripts/contracts/check.py create mode 100644 scripts/contracts/check_baseline.py create mode 100644 scripts/contracts/compatibility.py create mode 100644 scripts/contracts/generate.py create mode 100644 scripts/contracts/inventory.py create mode 100644 scripts/contracts/requirements.txt create mode 100644 scripts/contracts/smoke-consumers.mjs create mode 100755 scripts/contracts/test-backend.sh create mode 100644 scripts/contracts/test_compatibility.py create mode 100644 scripts/contracts/test_contract_tooling.py create mode 100644 scripts/contracts/verify_transcript.py diff --git a/.github/workflows/api-contracts.yml b/.github/workflows/api-contracts.yml new file mode 100644 index 00000000..da7ff175 --- /dev/null +++ b/.github/workflows/api-contracts.yml @@ -0,0 +1,56 @@ +name: API contracts + +on: + workflow_call: + +permissions: + contents: read + +jobs: + contracts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: pip + cache-dependency-path: scripts/contracts/requirements.txt + - name: Install contract validation tooling + run: python -m pip install -r scripts/contracts/requirements.txt + - name: Validate OpenAPI, source review, metadata and generated consumers + run: python scripts/contracts/check.py + - name: Exercise compatible and breaking historical combinations + run: python -m unittest discover -s scripts/contracts -v + - name: Check audited contracts against PR base + if: github.event_name == 'pull_request' + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + git show "$BASE_SHA:docs/developer/openapi.yaml" > /tmp/base-openapi.yaml + python scripts/contracts/check_baseline.py /tmp/base-openapi.yaml + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 + with: + toolchain: stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + shared-key: api-contracts + - name: Install PostgreSQL link dependency + run: sudo apt-get update && sudo apt-get install -y libpq-dev + - name: Verify actual compiled Server and Cloud route mounts + run: cargo test --locked -p marreq-cloud --test api_contract_inventory + - uses: actions/setup-node@v7 + with: + node-version: '22' + - name: Typecheck and test consumers of generated wire types + run: | + npm ci --prefix frontend + npm ci --prefix mcp-server + npm run build --prefix frontend + npm run build --prefix mcp-server + npm test --prefix frontend + npm test --prefix mcp-server + - name: Verify real Rocket and HTTP consumers with isolated migrated PostgreSQL + run: scripts/contracts/test-backend.sh diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index ffe282f4..b0b478f7 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -14,6 +14,9 @@ concurrency: cancel-in-progress: true jobs: + api-contracts: + uses: ./.github/workflows/api-contracts.yml + lint: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index 8f9af2bc..cefd10bc 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -12,7 +12,62 @@ permissions: packages: write jobs: + contract-tests: + uses: ./.github/workflows/api-contracts.yml + + compatibility: + needs: contract-tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + - run: python -m pip install -r scripts/contracts/requirements.txt + - name: Require complete producer contracts before publication + run: python scripts/contracts/check.py --require-complete + - name: Require immutable deployment-plan revision + id: plan + env: + PLAN_SHA: ${{ vars.MARREQ_RELEASE_CONTRACTS_SHA }} + run: | + if [[ ! "$PLAN_SHA" =~ ^[a-f0-9]{40}$ ]]; then + echo "Set MARREQ_RELEASE_CONTRACTS_SHA to an immutable source revision containing deployment.json and exact retained contract bundles." >&2 + exit 1 + fi + echo "sha=$PLAN_SHA" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@v7 + with: + ref: ${{ steps.plan.outputs.sha }} + path: .release-contracts + - name: Verify exact candidate and unchanged deployment artifacts + env: + CANDIDATE_TAG: ${{ github.ref_name }} + CANDIDATE_SHA: ${{ github.sha }} + run: | + case "$CANDIDATE_TAG" in + marreq-server-v*) component=backend; version=${CANDIDATE_TAG#marreq-server-v} ;; + marreq-frontend-v*) component=frontend; version=${CANDIDATE_TAG#marreq-frontend-v} ;; + marreq-mcp-v*) component=mcp; version=${CANDIDATE_TAG#marreq-mcp-v} ;; + *) echo "unsupported component tag" >&2; exit 1 ;; + esac + if [[ "$component" == backend ]]; then + python scripts/contracts/bundle.py --component backend --variant server --version "$version" --output /tmp/candidate-contract.json + else + python scripts/contracts/bundle.py --component "$component" --version "$version" --output /tmp/candidate-contract.json + fi + scripts/check-api-compatibility.sh \ + --manifest .release-contracts/deployment.json \ + --candidate-component "$component" --candidate-version "$version" \ + --candidate-source-sha "$CANDIDATE_SHA" \ + --candidate-bundle /tmp/candidate-contract.json + - uses: actions/upload-artifact@v7 + with: + name: candidate-api-contract + path: /tmp/candidate-contract.json + backend: + needs: compatibility if: startsWith(github.ref_name, 'marreq-server-v') runs-on: ubuntu-latest steps: @@ -51,6 +106,7 @@ jobs: MARREQ_GIT_SHA=${{ github.sha }} frontend: + needs: compatibility if: startsWith(github.ref_name, 'marreq-frontend-v') runs-on: ubuntu-latest steps: @@ -88,6 +144,7 @@ jobs: MARREQ_GIT_SHA=${{ github.sha }} mcp: + needs: compatibility if: startsWith(github.ref_name, 'marreq-mcp-v') runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 7c21b673..479399d5 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,7 @@ _bmad-output/ .cursorignore AGENTS.md + +# Python contract tooling +__pycache__/ +*.pyc diff --git a/Cargo.lock b/Cargo.lock index a07a0881..9130bfbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2994,6 +2994,7 @@ dependencies = [ "lettre", "marreq-core", "rand 0.10.3", + "regex", "rocket", "serde", "serde_json", diff --git a/docker/Dockerfile b/docker/Dockerfile index 0de03a8e..303f7ff3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -85,6 +85,8 @@ COPY ${MARREQ_BIN}/Rocket.toml ./Rocket.toml # diesel CLI uses /app/diesel.toml + /app/migrations (see docker-entrypoint.sh) COPY marreq-core/diesel.toml ./diesel.toml +COPY docs/developer/openapi.yaml /app/contracts/openapi.yaml +COPY marreq-core/frontend_compatibility.json /app/contracts/frontend_compatibility.json # Attachment storage (MARREQ_ATTACHMENTS_DIR). Created here so a new named # volume mounted on it inherits the marreq owner. diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index 868d4453..a644685a 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -28,6 +28,8 @@ ENV MARREQ_UMAMI_WEBSITE_ID= ENV MARREQ_UMAMI_HOST=umami ENV MARREQ_UMAMI_PORT=3000 COPY --from=build /build/frontend/dist /usr/share/nginx/html +COPY frontend/compatibility.json /usr/share/nginx/html/contracts/compatibility.json +COPY docs/developer/openapi.yaml /usr/share/nginx/html/contracts/openapi.yaml COPY docker/frontend/nginx.conf.template /etc/nginx/templates/default.conf.template COPY --chmod=755 docker/frontend/40-marreq-umami.sh /docker-entrypoint.d/40-marreq-umami.sh EXPOSE 80 diff --git a/docs/developer/api-contract-routes.json b/docs/developer/api-contract-routes.json new file mode 100644 index 00000000..fdfce3d3 --- /dev/null +++ b/docs/developer/api-contract-routes.json @@ -0,0 +1,1958 @@ +[ + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}/activity", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{id}/activity", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/applicability", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/applicability", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/applicability/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/applicability/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/applicability/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/attachments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/attachments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/attachments/{attachment_id}/download", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/attachments/{attachment_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/storage", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/storage/quota", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/attachments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/providers", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/external/{provider}/start", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/external/{provider}/link", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/external/{provider}/callback", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/csrf", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/login", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/identities", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/auth/identities/{identity_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/logout", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/change-password", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/me", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/auth/me", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/admin/backup/info", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/admin/backup", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/baselines", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/requirements", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/traceability", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/verifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/diff", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/cache/stats", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/cache/clear", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/cache/cleanup", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/cache/performance", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/cache/recommendations", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/cache/reset-counters", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/cache/health", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/catalog", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/categories", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/categories", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/categories/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/categories/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/categories/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{requirement_id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/requirements/{requirement_id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{requirement_id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/requirements/{requirement_id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/custom_fields", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/custom_fields", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/custom_fields/{field_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/custom_fields/{field_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/custom_fields/{field_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/dashboard", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/dsm", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/requirements.xlsx", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/verifications.xlsx", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/matrix.xlsx", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/dsm.xlsx", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/matrix-links.xlsx", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/requirements.pdf", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/report.pdf", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/requirements.reqif", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/requirements.reqifz", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/baselines/{filename}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/bundle.json", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/exports/bundle.zip", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/groups", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/groups", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/groups/creatable", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/groups/{group_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/groups/{group_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/groups/{group_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/groups/{group_id}/projects", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/groups/{group_id}/members", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/groups/{group_id}/members/{user_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/groups/{group_id}/members/{user_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/home/attention", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/search", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/imports/excel/preview", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/imports/excel", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/imports/reqif", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/imports/bundle", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/admin/logs", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/admin/logs/stats", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/admin/logs/export.json", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/admin/logs/cleanup", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/matrix", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/matrix", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{verification_id}/matrix", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/verifications/{verification_id}/matrix", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/mcp/principal", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/mcp/audit", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/mcp/internal/audit", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/me/permissions", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/members", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/members/{user_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/members/{user_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/reviewers", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/reviewers", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/meta/health", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/meta/deployment", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/meta/build", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/notifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/notifications/unread-count", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/notifications/{id}/read", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/notifications/read-all", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/notifications/preferences", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/notifications/preferences/{project_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/notifications/preferences/{project_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/.well-known/oauth-authorization-server", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/.well-known/oauth-protected-resource", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/.well-known/oauth-protected-resource/mcp", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/oauth/register", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/oauth/authorize", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/oauth/authorize", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/oauth/token", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/oauth/grants", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/oauth/grants/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/projects/{project_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/archive", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/unarchive", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/project-from-path/{slug}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/reports/types", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/report_templates", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/report_templates", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/report_templates/{template_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/projects/{project_id}/report_templates/{template_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/report_templates/{template_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/reports/{file}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{req_id}/versions/{v1}/diff/{v2}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/requirement-version-links", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirement-version-links", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/requirement-version-links/{link_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirement-version-links/link-types", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/requirements", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/requirements", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/requirements/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/requirements/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/projects/{project_id}/requirements/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{id}/versions", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}/versions", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{req_id}/versions/{version_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/requirements/{id}/impacted_tests", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}/impacted_tests", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/requirements/{req_id}/versions/{version_id}/approval", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/saved_views", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/saved_views", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/saved_views/{view_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PATCH", + "path": "/api/projects/{project_id}/saved_views/{view_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/saved_views/{view_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/semantic_search", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/requirements/ask", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/requirements/reindex", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/index_status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/semantic_search/status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}/trace_up", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{id}/trace_down", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/coverage_report", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/traceability/clear_suspect", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/traceability/clear_suspect", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/next-code", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/use-cases/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/draft", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/use-cases/{id}/draft", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/use-cases/{id}/draft", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases/{id}/draft/operations", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases/{id}/draft/publish", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/versions", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/versions/{version_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/links", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases/{id}/links", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/use-cases/{id}/links/{link_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{requirement_id}/use-cases", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{verification_id}/use-cases", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/draft/diff", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/use-cases/{id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/use-cases/{id}/comments", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/use-cases", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/use-cases/{id}/diff/current", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/users", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/users", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/users/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/users/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/users/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/users/{id}/password", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{verification_id}/control", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/verifications/{verification_id}/control", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/requirements/{requirement_id}/close_out", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/close_out", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/requirements/{requirement_id}/compliance", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{id}/snapshots", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verification-methods", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/verification-methods", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/verification-methods/{method_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/projects/{project_id}/verification-methods/{method_id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/verification-status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/verification-status", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/verification-status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/verification-status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/verification-status/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/verifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/verifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/verifications", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/projects/{project_id}/verifications/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "PUT", + "path": "/api/projects/{project_id}/verifications/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/verifications/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "DELETE", + "path": "/api/verifications/{id}", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/verifications/{id}/field", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/projects/{project_id}/verifications/{id}/field", + "variants": [ + "server", + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/register", + "variants": [ + "cloud" + ] + }, + { + "method": "GET", + "path": "/api/auth/verify-email", + "variants": [ + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/forgot-password", + "variants": [ + "cloud" + ] + }, + { + "method": "POST", + "path": "/api/auth/reset-password", + "variants": [ + "cloud" + ] + } +] diff --git a/docs/developer/api-contract-source-review.json b/docs/developer/api-contract-source-review.json new file mode 100644 index 00000000..7f587699 --- /dev/null +++ b/docs/developer/api-contract-source-review.json @@ -0,0 +1,378 @@ +{ + "frontend/compatibility.json": "0d9bcff76abe3a44b59c5bbcd3b73acd29266394e25c16b7c4b069bec28b9472", + "frontend/src/api/attachments.ts": "3ab5a6039eea83e9a84c1198e686802edbac7812821eadad84050da3a0b40089", + "frontend/src/api/auth.ts": "8b43e8187605037285550f751588ab4ed3b36b7813babd0e7ea397e9cb893f0c", + "frontend/src/api/baselines.ts": "2048ad67ce7d5bb56d6762e13344423e599b7dc71b287b26ae9dd0c07c2be022", + "frontend/src/api/catalog.ts": "9b80cbd031a50fedc09abc09f9784506582d68715ee92b56b4066faa1ab9abcf", + "frontend/src/api/client.ts": "ab4787005a7a4139996d857c4bf06414d8884d4462e3702452f9c1cdcf24df66", + "frontend/src/api/dashboard.ts": "2de1b33077d0de43ff695c222b90870bbda4916595332f80249796aac2f93c1c", + "frontend/src/api/exports.ts": "8dc5817e40727ced7ffc9b9f485b92bbf716764f22c3a48d0925a07a5cc48a19", + "frontend/src/api/groups.ts": "3e58295865acecb4f0fa18be7a5c490d7a011a4eab1bd771f2e6b7cfaa07fb2f", + "frontend/src/api/home.ts": "2454252b15757c234a781b6d14559d10f73ec0a468defc77c0407fa8877137ff", + "frontend/src/api/imports.ts": "72a998ce604f6b23158786b5992cf7add1ab2be532100be4b22ed9e77e6cd4d3", + "frontend/src/api/logs.ts": "9822d3be5e25aeb397fc6993740b9eeeaebca68262619e13e35f7c5d9ec95b44", + "frontend/src/api/meta.ts": "e4441b4ea3a89e65589e2d837b1f4a4e404061d3c8182cfc6b7839fe40c46e9d", + "frontend/src/api/notifications.ts": "35f6cdb54f1f1ebb229368d1a667ef1a3038600cf5f11b0614b5cc83a8e1ce89", + "frontend/src/api/projects.ts": "6bdf2aab505c5c422450fb6d375307d3fc6e01de4a5430f898ec84253a3fff40", + "frontend/src/api/publicAuth.ts": "207d53b750d9065911365f2a96c4d3a4a110983123b264e19b96da3ea0fd74ed", + "frontend/src/api/reports.ts": "1fc085f17031c28a011288dc6d85c5a87bc723b5007132f1769b886f41dbe560", + "frontend/src/api/requirements.ts": "9935a13bcc618a5b95be7d4a0783456d9f5fd9d32c3391964cbb2d3731471d78", + "frontend/src/api/savedViews.ts": "95a7dc49f2b7b217fc63d1032afc064b9a6aa852e513bf468a0f983073197760", + "frontend/src/api/traceability.ts": "7dcdab37cd865af634081bc8df0b05dab225e4df124f59fa8c9b526f2f99b22b", + "frontend/src/api/transport.ts": "25db6fdcde2a1c5c65e08dfce1a92f21e04b7b4b9a8ec2f613bbc0d2b63bf59a", + "frontend/src/api/types.ts": "9b4b6b7b71730b6ea0d214dbe087c264b864df91930f78ba88a7ed77be361489", + "frontend/src/api/useCases.ts": "1867c3aba24d4883fa243299067fff54abfcdb77195f771d4cd6bfef24c7832f", + "frontend/src/api/users.ts": "7155f0bc8f6768753d6a766ca25145259e0fd59c1321870f2967821e4c6151c6", + "frontend/src/api/verificationControl.ts": "f372bfaad0f15c7b112798ba54298776cb8993a9bfb41abde4dd3490974e5ad2", + "frontend/src/api/verifications.ts": "09315bcd631942a15aa01cf6d35e951bdf09c38f045572a50d86fade5c6b4042", + "frontend/src/components/AttachmentsPanel.tsx": "864c91baffb45c90703a7d6f5f4e7dd7245887c684e24b4931470dba39e42195", + "frontend/src/components/CommentBody.tsx": "ca3be1bd63cdac8761218a24e35445d33877e8d201ba56a4c99a7e82a11da5fe", + "frontend/src/components/CompatibilityBanner.tsx": "802755e5d6078469f55bc2fae084046d262709f1895e771e5f705a1efa766dac", + "frontend/src/components/DeleteProjectSection.tsx": "bed2e4b648e36d1a5fbf8a109a14279b87bb26d6ffeef445a971558c8d04734d", + "frontend/src/components/HierarchyGraph.tsx": "ea11fbe1e1eb949f23bc5913b9b182dabe796d9e0d8a79adb1a02d7474e7c7e1", + "frontend/src/components/MentionTextarea.tsx": "228f4f7b836b129a5a9d1e223b6c90f1d015fafee9c34d9626b61d569acc8446", + "frontend/src/components/NotificationPanel.tsx": "d787fad22b8bce38598991541da458f8b4c263131e07227e7d51df412f2b5c9b", + "frontend/src/components/ProjectCreateForm.tsx": "4432de404b310f026d9ad59eeca998b9b5502fccdf985f23a7afee95b560a0d3", + "frontend/src/components/ProjectGeneralSettings.tsx": "151ca068c4911be56f42ad14fb12530b95ac5c353939e432f2f5c18e54750358", + "frontend/src/components/ProjectStorageSettings.tsx": "0b77e7fbeb87ca2fdf29c4b316d87499c0505b38af6f7ab743f57ee1d2d7b60e", + "frontend/src/components/RequirementCloseOutPanel.tsx": "c9dab4f0c3ed9a4477414faaf869adecbc934ed39c216542c2c8a75ad90e45a6", + "frontend/src/components/RequirementCommentComposer.tsx": "ebe202796ba001fee0d63b492236a358ec8ef7d80fea330f5c2f342a2742b531", + "frontend/src/components/RequirementMatrixPicker.tsx": "814990316c34b18594959134663b4b1ffc27f0f3b12acf1c4d7a39db746d6400", + "frontend/src/components/RequirementReviewCard.tsx": "2cb83ac3cd3cc78a407c40dbcd56a127aa4426d6eaecb70a02a336331c2a7118", + "frontend/src/components/RequirementVersionDiffDialog.tsx": "80a6c1aa4b30da3693ccfb9614b05fad9a57fab40cbb9b652308b3235345bbb7", + "frontend/src/components/RequirementsTable.tsx": "c88067b10d2debecb6a1e887d1c5eff8be40a08f70191d69d1bcfe8590a377a0", + "frontend/src/components/SavedViewsToolbar.tsx": "b0dec9ba27a8055cb07bfd79d1c54f512432d1de67e314312125e8292425cfad", + "frontend/src/components/TraceabilityGraph.tsx": "6c74f0abc03db92a55c8b1d670185ae69fbbc3f5999d9cee409ac6c5c044de23", + "frontend/src/components/VerificationControlPanel.tsx": "994b105d7a29e3d8fb9bbe9d0dd78f97377f416415e00ba12a90a64027e94550", + "frontend/src/components/VerificationStatusMetrics.tsx": "0cf513fadec90402790797faf3a158b7c03c696c621fe0c11a3dac9c1fb21428", + "frontend/src/components/VerificationVersionDiffDialog.tsx": "532ff63107b952943f516cccd08e4bb2547b101db8b61c6cfb8814a7dcaa074f", + "frontend/src/components/VerificationsTable.tsx": "7712820673fea66961898d70ab98f60b46e823fec0370db209bce54ca7119398", + "frontend/src/components/dsm/DsmGrid.tsx": "417d102c6f81f220f026dfac83181262688d45cefab60a24ead6c4fd6104a4c4", + "frontend/src/components/dsm/DsmSidePanel.tsx": "795b7d6b208b8ac7bc65c473deaf58f2e19ff40a2d5496b35e82da481c00c186", + "frontend/src/components/dsm/DsmView.tsx": "f478d1cebd6d1ff7cad418eb9953c6dd7619b412ca33219176a1fb69eb02ab1d", + "frontend/src/components/matrix/MatrixGrid.tsx": "2fff2d35fb634a7f5e1e2f08a8e63e75b12ae91e0fdddc6e8abba263d40cd759", + "frontend/src/components/matrix/MatrixSidePanel.tsx": "e12b080999f75d1e5b93f4f0ad77c22582807a06e41807ed86cd78c7540327ee", + "frontend/src/components/matrix/MatrixView.tsx": "8b173ad6c53f9082e8a4c28db5d6d65cdb26faf947d07426b28eaebfe3dcaf0d", + "frontend/src/components/reports/DocumentSettingsForm.tsx": "5e5bda5e256db2cb0df6f6745a0f56059cfdb9df8cda31d668103cc3a6919d1e", + "frontend/src/components/reports/ReportDocumentsCard.tsx": "ea4599e550fc798fd6c55b62788f01f503c5b0482834e7e2d4128bfc0caec4b3", + "frontend/src/components/reports/SectionList.tsx": "7adb1c33b62f3ef05e0db15af981219799f1e993ebf4da5c73d3aa1f81907bc4", + "frontend/src/components/start/StartSearch.tsx": "0cba50200262e52bda2fbf22e7c57803e1d2db0c2faa60eb5587d8af4af2a167", + "frontend/src/components/useCases/BaselineUseCasesSection.tsx": "b4d20fb9a89f5c730379aab6c3e4417133b38f700f39e79a4e64c7cad202a822", + "frontend/src/components/useCases/BranchCard.tsx": "2c16c584c566340617833503e180d2be175e7415761d8455b9457061834fd4f2", + "frontend/src/components/useCases/LinkChip.tsx": "0ca3ea3f0c024c85eec9d5d6dd2ca4525f4d06b5b74d021b2a72222295590ca6", + "frontend/src/components/useCases/LinkDialog.tsx": "0db09520c092df0410e9e65231e94e4005daa64c41fd56b7ba180c758e3fa782", + "frontend/src/components/useCases/RelatedArtifactsPanel.tsx": "03fbafa70a47258bbe715c9d2005bf26a6bf677b6d3926bb98f6c9582661cfdb", + "frontend/src/components/useCases/RequirementPeek.tsx": "09d3b1de5afa65ab1ba3e3add96fe3cd9ca12aef944c5ac29adffa6a91a351d6", + "frontend/src/components/useCases/UseCaseDiscussion.tsx": "73f54e3c66cc786226fce62764c7221d34895dcb46fd004d60dd0901a7954544", + "frontend/src/components/useCases/UseCaseDocumentView.tsx": "46957ce3466f1a0d4ac45112c1229a2c68fd428fbe515d1681faad3fced4b405", + "frontend/src/components/useCases/UseCaseFlowView.tsx": "ffb2b6acfdebc2e4fdd5df4b6f1ba974d07aa45556b57e4124d505257dedfe22", + "frontend/src/components/useCases/UseCaseReviewView.tsx": "f642c462715da9f95cbe0f0791ebdbe77bab97eabd791ba41abce87048dbf583", + "frontend/src/components/useCases/UsedInUseCasesCard.tsx": "cd65c36495771faac75eb6104cc5cc35c50c22c42247d3c8db7d6208bdd2a427", + "frontend/src/components/versionDiffUi.tsx": "288799d997ee305283411d9637910a4a9eafa6d57d6416f3ad7b14b1f7915a72", + "frontend/src/context/DashboardContext.tsx": "fc5b37e22e5c758062ade4f23474186d06fbfab39830ddd6caf4b56340762a81", + "frontend/src/hooks/useBuildInfo.ts": "444733da6fffd5734a5511370d1ed3253ea6d05b2425d91bbba0aae4fed12b77", + "frontend/src/hooks/useNotifications.ts": "ff1cc4fbabddfe4f91c1dd98231dd082c5eb812249020d577ac8962abb383124", + "frontend/src/pages/AccountPage.tsx": "2c6b72c059ed8585e92a7bfb3cbd6ec01066801d27a00fe1ced770671cfaebec", + "frontend/src/pages/AdminPage.tsx": "7cac5d4dc76bafc498831509c34c8870f774bb75cc9a816d63e48f3fab2d1a98", + "frontend/src/pages/BackupPage.tsx": "aa5912d7f6e4b76f7759b790951dfd4fb5b0748261ca928488cb590465abe8cf", + "frontend/src/pages/BaselineDetailPage.tsx": "590af60312aa864a7bda51be8f85e04c2af90edcd3a1e1bead7e74c497ba53a2", + "frontend/src/pages/BaselinesPage.tsx": "efec445a6c5f8b8062b0817cd49673519a6da4e401d353d81b802dcd95b80fd9", + "frontend/src/pages/ChangePasswordPage.tsx": "0ff12abd9c144cfc2fa1528cdc3b4076b00b686bba48c7ddf567ee6a0b4846ad", + "frontend/src/pages/CreateRequirementPage.tsx": "b7b87777c9438ed186c9bdca1f7533e4b8d68520efa3560c6edf13f64cb75cef", + "frontend/src/pages/CreateVerificationPage.tsx": "c638e2d1e8be63854f0acd4b167aa575fa85b18119ef10b2684d0dbd3f2c204a", + "frontend/src/pages/DashboardPage.tsx": "5ec394dbe016542d4b85a031aa589aa360e9ab2e336e6eab6389fb763c65e806", + "frontend/src/pages/EditRequirementPage.tsx": "acd9281739516a859e32111db4bb5a224e529ad6b3f77e77d6a5790b036278b4", + "frontend/src/pages/EditVerificationPage.tsx": "f9f7a455dd564269e3d02be7e7f156aaa27a37c9b41e0520c42f389891d145d4", + "frontend/src/pages/ForgotPasswordPage.tsx": "c8beb4f6ab9869c01f6fc3f08f2b624c6f2828e41c9433d285e0b2827dacde45", + "frontend/src/pages/ImportPage.tsx": "3d64cecd82bb0ac326202940401155aeaee3e24da2b6fa1f2f9f6d557db8cb07", + "frontend/src/pages/LogAnalyticsPage.tsx": "b5e389677fe998fdd60e0daca5290de48edc17904cd07876c51ca317d4ae3e28", + "frontend/src/pages/LoginPage.tsx": "574ca76f3b7d20021aa66ca786fd7a46b088e340710dc50f658b0ff0c14a99be", + "frontend/src/pages/ProjectBundleImportPage.tsx": "4162d83cad0c9633ba392bf065edbfd93acb9d99ae64d9ddb4506828f2a2d525", + "frontend/src/pages/ProjectCreatePage.tsx": "17e145d53aebdbde58753a27b093c5b7a70cb109ca507d6a4f2c504fa0743317", + "frontend/src/pages/ProjectLayout.tsx": "3bfc38ff8fe368dc8ff25ab05f5f6acb5ec37a33aabfb4e10d5ba8e849a9322f", + "frontend/src/pages/RegisterPage.tsx": "1e9e057dde4e9faf2c39e2b5a1f8aa4ea8f6c01ee802006464c78bc2f5e40f88", + "frontend/src/pages/ReportBuilderPage.tsx": "8f5d61e7801a1a2deb1b5e51108fec0380bd4d83dc6cc839820d818696d50f3d", + "frontend/src/pages/ReportsPage.tsx": "1b37d6a6b5ec8b8a5fe116f7f8fc070a95524f36b1b5373a9aa76e7fc1abaf94", + "frontend/src/pages/ResetPasswordPage.tsx": "bf60116c75c4178d98cb52a25e4c74fce8cfe7b01f3868357a055f95262ddf36", + "frontend/src/pages/StartPage.tsx": "92e1ab484cf0c151118aef27ab0278f9f9a57a141e7230f2572a493830591205", + "frontend/src/pages/SystemLogsPage.tsx": "cd505938abd22298c9eff02316c2e1d12fc58aeaecd0ba07c2d2fb8640a4f537", + "frontend/src/pages/UseCasesPage.tsx": "8b7bd84008cdd1005f7177912e23306951fb58f04ca070f217a230fcb2e6c80e", + "frontend/src/pages/VerifyEmailPage.tsx": "d12d0483bef2b7f1242b70cd49de89671ec39e04e557e5183788cf3b0e85a0b6", + "frontend/src/pages/ViewRequirementPage.tsx": "32cd5e22c24aeae559c87f06a7f5daf7bced700fb1cbbbfd489c98e84739b2aa", + "frontend/src/pages/ViewUseCasePage.tsx": "1cf82735d28145d518dc3d7b9f4970c6a9afe735707000c511a2fee85e3c65af", + "frontend/src/pages/ViewVerificationPage.tsx": "333753fc8f69e2c69579097191e1118fdcc2b51309c0c915e394775aa7560a2c", + "frontend/src/pages/account/ProfileSection.tsx": "4853064f8b07d51cf97c2437b021fec43c5e7bc21ae54f1979242021cfda9d0c", + "frontend/src/pages/admin/AdminLayout.tsx": "fcd781952701cf7b14a34077669d15e0daad07aae8f7318c9f5d61969c617849", + "frontend/src/pages/admin/SetPasswordDialog.tsx": "66dec75ff74ae92c46ff6539786caccdd7075b5182b85ff022772dc2a5493729", + "frontend/src/pages/admin/UserFormDialog.tsx": "7b90758cf2349f92173da92a31a250aa0cb7146e4888dadf862cff611b5b9571", + "frontend/src/pages/catalog/CatalogApplicabilityPage.tsx": "3e0322240e4da2a13854fa061207987dd97f65d6efb016102a6335a6977ff804", + "frontend/src/pages/catalog/CatalogCategoriesPage.tsx": "c4819f09441e4d435f6b2970ccfde47bba93aa9a8bc27c2f6741962d8e8e2176", + "frontend/src/pages/catalog/CatalogCustomFieldsPage.tsx": "48009a19ac670cc45c79894fa425eb9c6b1e9e7162dec18fb7a9ac1f59c3e390", + "frontend/src/pages/catalog/CatalogRequirementStatusesPage.tsx": "2e49517a487c93a32bb493ef5455073da81000b8378cb2a7a80638f95185d4c3", + "frontend/src/pages/catalog/CatalogVerificationMethodsPage.tsx": "5034a088d804caf187109a9709cea94e1773166b6e55b48ae9a2c1c83c2f3fe4", + "frontend/src/pages/catalog/CatalogVerificationStatusesPage.tsx": "8a24da86a3a6d98d0dafa4f1ddd201dda014880ef420502ae61f2c02796cc231", + "frontend/src/pages/groups/GroupCreatePage.tsx": "ef599da27c8b439dd77899b5b6c7e9378be83af68114da5cf321c3d5f7f6edfa", + "frontend/src/pages/groups/GroupEditPage.tsx": "16c192ed8e7c060f5e32a701c2e93ca4cd03e0431e381c13c999c127996ea995", + "frontend/src/pages/groups/GroupMembersPage.tsx": "29a3f2c44b8c374b8569e49dd341e8e403cfb0125e40124409f469ffe9ef4aca", + "frontend/src/pages/groups/GroupViewPage.tsx": "ab42dc3302004af815427e247da6c66ed9485d3190b56690eb8687a84d72884c", + "frontend/src/pages/groups/GroupsListPage.tsx": "8ade08bf0e4815ee866e4bbbe1de1a52592de1cba57659e52c69a9b44f6d4530", + "frontend/src/pages/settings/MembersSettingsPage.tsx": "8a9089ac90817ad47a4306b12081e7b262142082c9a545f864f45700629e44b2", + "frontend/src/pages/settings/NotificationSettingsPage.tsx": "45a06bcbc7c68580e11a1dfc10d92a71fa07d63e8636c2f70db0d2e9fa37db4f", + "frontend/src/pages/settings/ProjectSettingsLayout.tsx": "59eaadac194a13684ffb2285cf2f6d9bcef3d781e9433eb3a6ac439c5b188383", + "frontend/src/pages/settings/settingsContext.ts": "43047b5fda62fa4131d7928842ddaf6e7600c90b684abdc38b8c71ee918765f3", + "frontend/src/statusAuthorDefaults.ts": "fbcbdbfa7e1402ce194a0796a665bb8fc7b22da209cdda630b6e19798c67306b", + "frontend/src/test/reportFixtures.ts": "126979fb70781d2c29175510b5162c37eb62442ea66a2f60243821aca68fce60", + "frontend/src/utils/dsm.ts": "f9248410826e11d2eba0acbadac7877675e91acf7d9e17607409c8db4e561c52", + "frontend/src/utils/duplicateRequirement.ts": "dd9ae9e091d773f2dbe4edd75afbc548e8d90f71c85dfda9be974073703ee4bb", + "frontend/src/utils/mentions.ts": "ac1f4b8a4ffc2086f945315e40b7001d03c48ffa42a93875d3f09ea06cb110d2", + "frontend/src/utils/parseUser.ts": "528441744ab75019bb683f3cd2e5d142abe425e63d4407592c1cbcdd37957a7d", + "frontend/src/utils/projectUrl.ts": "146123b4a0a460339350dc7ab96dde0649623c7a67cff68997fe005a6e4cbf2f", + "frontend/src/utils/reportDefinition.ts": "f39b5528889f7bd0fbd5668e5cc8eabc296db77cae433526dccb036df913167b", + "frontend/src/utils/savedViewDefinition.ts": "a1d1251f7d60ea03c4b3a8a55f75722e936bdf30dd6f370b836fa42fba3a0f7c", + "frontend/src/utils/useCaseDocument.ts": "a51556ac25f9e5bd3c4c92328a86738bacf2c45a0a42608dd858821f3c492de4", + "marreq-cloud/src/api/auth_public.rs": "b080c11b42d95b02a21b6f49226bd1c1c9f72a6eec4f2056f6a7f29ef4def378", + "marreq-cloud/src/api/mod.rs": "0a570340b80f76ced32d7f30c057c16d95dad39ef433180911f7a8225da18919", + "marreq-cloud/src/deployment.rs": "6833286dc0ceb2933e95a1bb884a5174548bd801b645b97ee05d0d7ca042bd66", + "marreq-cloud/src/fairings/cloud_admin_bootstrap.rs": "d9cad7b7e1c71c071696fee5bbabd387ca438e741a821e577b26fa0a8c65ea01", + "marreq-cloud/src/fairings/mod.rs": "6c14ec5bd25e8835c4b19cbdb1674a3540ad7cf106dcd43c136e9ddc263db272", + "marreq-cloud/src/lib.rs": "8f873d14051b1b1c177bb9b66eb81b9430f970073219d8cff8deb7834c2cf4fc", + "marreq-cloud/src/main.rs": "45f07e434e587a3c8ec95fec94b127d43fcbb395ae9da6351ea4115ba07dcdcf", + "marreq-cloud/src/routes.rs": "36ba1db8aa241bbd96757d341bf2058e2bf650019242ee2a09ee90d03b833905", + "marreq-cloud/src/services/mod.rs": "4607f9a7c5c8651472b8016b6ac8f68ca3431939ee4c3f9d0e944422f7348a25", + "marreq-cloud/src/services/registration_service.rs": "433de11216e66c659fca1572d2c9326c7f27b58076a6c65bbc92d4bb876eef41", + "marreq-core/frontend_compatibility.json": "3e569883e23b5411ca13570743096c893cf876362060007d0a17a8ee5623d2a7", + "marreq-core/src/api/activity.rs": "94a3fd9c6aa73ac989935925f108d35db55b148cebbc6dfdcde62b8872d1809e", + "marreq-core/src/api/applicability.rs": "aa88a53ccdeda77df4d83f7bffde00668bf6152ef1e7b598ca9179ecb472b081", + "marreq-core/src/api/attachments.rs": "a7f600d5e6293038ee668ca673ed093be06714e1191a06b640bb99cd11f56124", + "marreq-core/src/api/auth.rs": "42c42fe249ee62a7e5bbceb05030bb63f50806ba4c8b9ca36ceaa49b5e372ba1", + "marreq-core/src/api/backup.rs": "a364be4c85072f311d0a8e0930ec42dae25182ac035cbe4ee9576e2d1d74cbe6", + "marreq-core/src/api/baselines.rs": "53bee136e032cb6f167d639c06d9feb7f230378a88a5fee6de5819611b3c239c", + "marreq-core/src/api/cache.rs": "63410e53b7e2c01758bf565af66981dbd5bd046fd222d5ea9c6b5e4a6f571c75", + "marreq-core/src/api/catalog.rs": "8a7017fff0a5faefe8d124aacc0ed3f1a68c5289398969391bec3d40c9cb53ae", + "marreq-core/src/api/categories.rs": "af9ebb58df5c50e7d1a590006400ce2f5113d846b481cc5e32b6a1a0d0f070b5", + "marreq-core/src/api/comments.rs": "4d5429530e90a4f9fb4cf5b7c224c7744a5c89b4dd14d6c5aa11c4b4c09e161c", + "marreq-core/src/api/custom_fields.rs": "7138b0fee7f5c2a938c277413f7bd8289c4ffaf9ecf70635f9eecdd4d3c5b48b", + "marreq-core/src/api/dashboard.rs": "d262e1de00a6b180951ac80891c6ed2574a5b96f939b2cee4bf21c4a80952665", + "marreq-core/src/api/dsm.rs": "609ccf610c64d69438c28fdce7623cf81ff68a1f91f0f7f9755ad0985d81c474", + "marreq-core/src/api/error.rs": "f8703993e7a5c266b2cb7b946246a5cf285a46ddda9d4f976e8a1d0e4f6c3d0e", + "marreq-core/src/api/exports.rs": "ed842ab4cff67fd556e779bf65744e4bf1d42e8bd3513a10be4cd3a1731ae40b", + "marreq-core/src/api/groups.rs": "3d8f31614abdc70bb2f9dddb08d1b8464556d5a04fccffd16c337692d91a60ad", + "marreq-core/src/api/guards.rs": "ba11edb5858a725a36e1988a7ea76d1ac0642945bb6c3f8406eee31d00e7deda", + "marreq-core/src/api/home.rs": "3b8f325d7f1096734378702bf42c72e8c7adb0793b7c77ce51478b8f6a18dfbd", + "marreq-core/src/api/idempotency.rs": "7586819b8488daed5c7fb51495c70c9369a8cb069d1f9aed76cdb0ebc12e6f8a", + "marreq-core/src/api/imports.rs": "d76d3923cd34f58f02b55a600c56e2491b4b14c8269bad23c9ddd137343e518b", + "marreq-core/src/api/logs.rs": "126db136b1bee0f78227cf7be98fd330309700ecf6981acd4fd1dfeac74bfc2a", + "marreq-core/src/api/matrix.rs": "951a83ff3feaef5b94b902b8101779873d5075eda609946376a7668338d02f22", + "marreq-core/src/api/mcp.rs": "01451ffb92308c53634718d763433e60b1714c42f2f02a33d3e1a7c46338a0cd", + "marreq-core/src/api/members.rs": "398c1bd74615b611c6fd71cb3d54b2c26c077b90d0db5b52ea8197fe1fb99a83", + "marreq-core/src/api/meta.rs": "13ff923c9635214132f8e5ac6be236fa9e0dc0eddca7ea826993655c4ce0fb2f", + "marreq-core/src/api/mod.rs": "a8b106368e1fed4766fc75c8135f5ec63e867452e18afa096afabd66012d1a2c", + "marreq-core/src/api/notifications.rs": "ca1f959e06e5f8e8d8e55cb4d83342006378be4c778ac92cf67e1c885d9b0a74", + "marreq-core/src/api/oauth.rs": "ce1a150b3c3b3ef2118de60767f95ac502000c29ae94520b5bbf1d14202eb801", + "marreq-core/src/api/prelude.rs": "3aca18c037023d5076101771d0c32045fb8cd2dda3d8410bdda9f2b6241ef9f2", + "marreq-core/src/api/projects.rs": "e607a66955222dad5ad6fb29daf8c881fc730d12fbc4c391bdebf7664d97ca2d", + "marreq-core/src/api/projects_session.rs": "06a1689822c8956ff4570931cdc056a198e0bfeae85b6e171ea11546d8c3c840", + "marreq-core/src/api/reports.rs": "09c31e4c57508f6aec4b042d86e87765690f82ef64a5c6b0fde233fc2a190e10", + "marreq-core/src/api/requirement_diff.rs": "39a662d096001564033e2069c67265b797d5b7c5eee45bfe26cdeee1c04cf637", + "marreq-core/src/api/requirement_version_links.rs": "a2790428840fb0c99a874f26ea524b6649ab3760b5218b8bf6ea20c0d6510848", + "marreq-core/src/api/requirements.rs": "bf2317f5637bedd763f6a37a9b015fce0e064c58315530a0f6430724606df93a", + "marreq-core/src/api/saved_views.rs": "ed3a13e3d95d56b1a6a2f41e123a7dc6944ab1b534845c3c8309de6c9f111d6f", + "marreq-core/src/api/semantic_search.rs": "6e4da1230a89137f3339fab09750ed9b8b773a3eb2722680909b32fd69a92ccf", + "marreq-core/src/api/status.rs": "4c1ed40c20c74cf541677f8573b60af5afd4eb32decda9a4d9d10b69082c6299", + "marreq-core/src/api/traceability.rs": "b45e4e0ab57e9fb8da0d4bb58e32945b4cb8cf9d2f175d548502360f411807e4", + "marreq-core/src/api/use_cases.rs": "3355a964773df4500bb173bac7b1d78ce9e2110e48c6e628bfa3f6f249b1e766", + "marreq-core/src/api/users.rs": "98bc2ed2ce937ef745399178f78b59517ec10a8677497ce4f7c8a5c6374bbe66", + "marreq-core/src/api/verification_control.rs": "5f49241dc2ceae386381c3c5a088e75e7e67d5c5140ef8cbd0540c021e83882c", + "marreq-core/src/api/verification_diff.rs": "3c1957bddba35bf6a3c88e91e02353584aaf52dd8fbb853ccdef812fd2fd2fc7", + "marreq-core/src/api/verification_methods.rs": "092ce0a633249acfb9f387ab859bf832512bdbb68277c906084e8929c11ba58e", + "marreq-core/src/api/verification_status.rs": "91fe8437410198511618bbc7638480be7c0e6dfa1c63ef67ea45733dc070b746", + "marreq-core/src/api/verifications.rs": "77e5d01401363a2d0a26664b9015ca836261a108aba631d9d2a6b5468b285f71", + "marreq-core/src/app.rs": "f506f1951a8db3660348f69630a7e77fbe5109a0365f0a26fb49fc0049b50cbd", + "marreq-core/src/auth/config.rs": "fe55c128b1c8886451a45d5b0863840005de6afda5f16b77573512885e33fbde", + "marreq-core/src/auth/csrf.rs": "a988dc55f7cdbc390072fcdd91fda6f43233023c85d05a04c036152f95ad9b0a", + "marreq-core/src/auth/delegated.rs": "61a549874674294a037394625ac81b05844f88627b5531cabf812bf15174c3dc", + "marreq-core/src/auth/errors.rs": "acb72478d2c6bd08f59efeed13d01d484c46a7024dd791c65910948abd7f022d", + "marreq-core/src/auth/external.rs": "bf0c4a33b3b849de41a31c7761f9478eed37f55e6e320fa5640a6b050f0b9bf5", + "marreq-core/src/auth/guards/admin.rs": "bdc1c1654926cb9b5425897371c87b402393d2d5c1fae29cc8fc37f40f3d1df1", + "marreq-core/src/auth/guards/api.rs": "ffdebb6107730290213f249fceea8a5796089339ae5a8d8a61014d074917150c", + "marreq-core/src/auth/guards/bearer.rs": "138e9c96e04372e8f2c3f33c298146bfbe128bf53ddd94e147a27856b41ee7b7", + "marreq-core/src/auth/guards/group.rs": "2042fe1d4af6350d0eb8a42e23ca5a35452cc8e9bd80535cae13154f0cc78f48", + "marreq-core/src/auth/guards/html.rs": "21529d3a0d23ac112f57d8cf7be81bde9824a42a9d44ad48bd91a0a2798b2389", + "marreq-core/src/auth/guards/mod.rs": "09e01eb7522a013007167447d0102fd7173d8588e1803fbc406748eeb5dda7c8", + "marreq-core/src/auth/guards/project.rs": "3a03e405327e02f35f1b69348a03b0ae1762d0e19f24c9f46acd0bb69da341d0", + "marreq-core/src/auth/guards/route_params.rs": "e076f960e7316ab45e33d78419296dc56ffa1020dcce95711f9865bbc4620fd1", + "marreq-core/src/auth/guards/session.rs": "b290b71d9d88f340f955b205cefc9c726728ff887b4014b803573fb173c39563", + "marreq-core/src/auth/login.rs": "48b258c2467fa4b7b82f07d9fba566be8efad302fb92ec445580ee6cc3705326", + "marreq-core/src/auth/logout.rs": "04e469e64e7bde1c21e5aca119f2ca166e28c747976b7ba494550275b65e6d03", + "marreq-core/src/auth/mod.rs": "ba9b96813124d3c439033a0fb5ff4815d48f0383bc05de9878bbfca8cf92938b", + "marreq-core/src/auth/password.rs": "1babc63db0d5adbccbc5aa94d378ec1b5a9ea962a1a40b642f308a44f75c441d", + "marreq-core/src/auth/password_policy.rs": "038967fae772a36137fc236b4e618776ce7a21ce53b80e20c1b5746919b6b9f0", + "marreq-core/src/auth/provider.rs": "cf7dd4fb37f566af283bd876cc0ac492b5c4d1e275e70c8f3b170e682ce8ebb2", + "marreq-core/src/auth/rate_limiter/config.rs": "9c225c383fd2fdb444199103a3dfc8ad892ae387c073162badfe64dd1df7713c", + "marreq-core/src/auth/rate_limiter/mod.rs": "f57a3c416486907adf7340d86058352046b553a1fc5a8de5b9d4e2d45913e8e3", + "marreq-core/src/auth/rate_limiter/postgres.rs": "4324ccc8fa90bfaf1e79bd44907f758bc2e07d72a4d34839b71afa7ade845428", + "marreq-core/src/auth/rate_limiter/store.rs": "7b8e861c56f62331d05996b7dd74d8f0e970fb1ebfadfc136e3a4d5e9220c0fd", + "marreq-core/src/auth/session.rs": "5c1df56bf8fc67ef2782d9e2781074ee6a366add00f997ca4e67fea5d06485b2", + "marreq-core/src/auth/session_config.rs": "732703ffde7f9f651b51c47ff544089edfd4fdda6fcd11ddef54e37968c2f605", + "marreq-core/src/auth/transaction_cookie.rs": "de91b4949f98267b232a3b3152460053ab16f3315e8821bb7186e28deaa4b069", + "marreq-core/src/authorization.rs": "4facd135c6f2b3c9c16249d9ab63fe4cf9d1fed8afe2190fb9d403d671f7d44d", + "marreq-core/src/backup.rs": "7e0223d93afbfd52dc0395e023c4be7156cb2aff193d27ec00557731754b19fb", + "marreq-core/src/build_info.rs": "3f602ae76288c24acc8efd5e445a299ca3c671c4b69b1c2695adc68c184c142b", + "marreq-core/src/config.rs": "44b3bfd89821e6965da447dde30045b181afbdd51e6540e980ce0912d6d40621", + "marreq-core/src/cors.rs": "31aa625f8ed94d43eb41e4d29c2ce35a30b61256b0388abf53dc30d580fc23a3", + "marreq-core/src/db_types.rs": "080883843e7ae0facaf671951d3985fbf8614f956dbb24db4c3f1624cfb206af", + "marreq-core/src/deployment/mod.rs": "7ca21f6bb56fc06009f4193c4509eb4b44622825ba33287a002e0a2f6f9a3873", + "marreq-core/src/deployment/test_support.rs": "3678a6ea0d4b989b14e630c3a9283b9a88b949e4b256e74b428df076308659bf", + "marreq-core/src/diff/mod.rs": "de6112f9c9731040d1af1afb2637863ae16c814105caebef2ba16bb9efdde7f7", + "marreq-core/src/fairings/cache_control.rs": "3a27895c3f0e2f5c9d41f922ac786ed59848ba5911434212c784daf270f1516e", + "marreq-core/src/fairings/csrf.rs": "be695fea17b384d081b2aa3dafa2fc0934c11b33cee48ef2a16139cb8a396d9f", + "marreq-core/src/fairings/mod.rs": "bb30f3b994c05075094bd4d2b91e96145a9f4daff1fcf0dc2b06206029854cdb", + "marreq-core/src/fairings/oauth_challenge.rs": "d09b75d9b7a82bdf232700dc4f8eb66a30a5a524031558da32f5c3f4b22859e4", + "marreq-core/src/fairings/request_log.rs": "0c1e067a3977e5c9d7ab77f58f188724d5d8414ac362e10a61822c8dd4ccf839", + "marreq-core/src/fairings/security_headers.rs": "f97b003cfb9a73d42aee51c4e2dcf94064b779f638a424be917a95c4ef89deed", + "marreq-core/src/fairings/semantic_index.rs": "498f6e3a73719bc92b3818b855a837da300380dd8c868ae9562c401069fa3abd", + "marreq-core/src/generators/excel.rs": "6a1ff8ca77c14227e5e93b086c5cc051601d1b47305dfc0f781055915a8ae57b", + "marreq-core/src/generators/mod.rs": "c1ccb1039a1282c40f4813cd5cd1ea598449f539e303784f7ac27754fb7c42b8", + "marreq-core/src/generators/reports.rs": "97cd2c6d58572e65f18dd20cd3bf68a193b610173d962e37781e2f4a04550958", + "marreq-core/src/generators/tests.rs": "d75f80eb06e806fe094672c361284267128c5400e9ad986f7f30fadf8a09c1f5", + "marreq-core/src/helper_functions/decorators.rs": "a1e66680f74295c681054eeb2b00dfdf859c6fc41fbe77e1fba6d9d3b70b1f32", + "marreq-core/src/helper_functions/filters.rs": "cba543dae55a85d7e45c304483856c3adfc7be988d6268f7bc63e1d6e5300976", + "marreq-core/src/helper_functions/mod.rs": "4c8fa19cde6104e8df42dbc0e5d4393d3ee1af7b2c83777d2add317f880991bf", + "marreq-core/src/helper_functions/reports.rs": "2b7cbd806ac46dfed4a836fa62cc6ac3b14bdbf8f8e6bd7019e1b1b96562154f", + "marreq-core/src/helper_functions/utils.rs": "ca5c701668ca6859527d185cbcb5574b528ada66e6bde154f1200eeaff6b7bc0", + "marreq-core/src/importers/excel.rs": "6fdea2824c9650724056618c4d17f5e27b347f6169be2365e33df4e32c4d71ec", + "marreq-core/src/importers/mod.rs": "c737520bbde0e3ae1d3f7e8b2f66bad2a9508f1da01116f8c190d2ab3c8543e8", + "marreq-core/src/importers/project_bundle.rs": "538e47b3b288ac938c7f6063e8058524b80f7cbdef16ba4ac8a648ffaabc93fb", + "marreq-core/src/importers/tests.rs": "e0f5331e87fd77c7f468199e75f4d2437ba5726bc8c975a957be734ec9399098", + "marreq-core/src/lib.rs": "1455ce36de9647931b03fd18c1f4a1c82a6df41e2df40948805996fbc4a7da6e", + "marreq-core/src/logger.rs": "b6a29960712e7fa9ad9ebfa33c041daab6e5a80d350615237903921e17491e85", + "marreq-core/src/models/decorators.rs": "40bb2f62a4037fbaf9f23aef4400606f4640e5f37471a4a8923e0dc46f7bf3b5", + "marreq-core/src/models/entities.rs": "1eabe06f2e169a5300cef45f85e897a9611233b1fdc0ac743a9bbbee705ac5fe", + "marreq-core/src/models/forms.rs": "7dae5b25d0cda8f5d943df5e546cd3e12fe691872052d475dd8f023ed6402c20", + "marreq-core/src/models/mod.rs": "649f0f88778ac1a4145ab00a07d685277cc365ce4be75801b1f462ad1ef3cc1c", + "marreq-core/src/models/semantic_search.rs": "3221de526e270bcfac49df625d129d93df0849137c201ecd58b0b0afcc9d8370", + "marreq-core/src/models/tests.rs": "66760a34dd08e200871c6bcddf0cd3572cebfea7ea3c3a11e5d7ff5b85c9641b", + "marreq-core/src/namespaces.rs": "0b7c8e3d7c437e8ee50daaaa81d578d21f20543496801bea7032fb9a4b8d12c0", + "marreq-core/src/permissions.rs": "aba57beb9f1ae6583e3c79c07530085328befd1b2cba7a2d925cead7eca10a3d", + "marreq-core/src/reports/data.rs": "2a3611d90d886b15dd8fae7f054cddd79aa42898d7a8855d5c0c34ad1533f3f5", + "marreq-core/src/reports/definition.rs": "8131924beb38d768c09b2e8c64c535de325a215eee0a1104e1b055da5c1a550c", + "marreq-core/src/reports/mod.rs": "dd04e70a3dd01e41561d0cc986655d08b1c70707dac2100803a4e4d64f5c8671", + "marreq-core/src/reports/model.rs": "8bd9b9d2b37f50d6e5058b507734d50c5685b98f8620cbfaaa418316d5eb916c", + "marreq-core/src/reports/odt.rs": "1990b981599d0c99bb296877e8806c4635098497903a50b60cc8d58ef98d4bfd", + "marreq-core/src/reports/pdf.rs": "05dd88da08ca9bd9dbceafd09c1c4a978505701e9b60cbe3c99ca83a41ae0b46", + "marreq-core/src/reports/sections.rs": "edc74cc6432a9ae0b0fece0f3fe2590173cd50f491e691e2161a30a1f5b266c4", + "marreq-core/src/repository/cache/keys.rs": "4480d4a36550e69a86162d029b9fbd18cf0f1ef7d2a9ad9ec4fb7fef0dba63f5", + "marreq-core/src/repository/cache/mod.rs": "70280d28b9fe0df543ba951fac7249ad498b49c206536f2dda32ca076df98349", + "marreq-core/src/repository/cache/repository.rs": "06eaec21cd1b7aaf219b39790076fbf400e07d0172bbf4ea10cfc0b84742d60d", + "marreq-core/src/repository/cache/stats.rs": "6f7c20f039b29f6060a1d76c3e8f11aece1828406a347dbdc6f303d06c8182f9", + "marreq-core/src/repository/cache_middleware.rs": "700959d0e2161082f1e2df92b1fd7309f8869fcb05679fd22b12c8e76f504269", + "marreq-core/src/repository/diesel_repo/attachments.rs": "995a5bf18dd274109d235de35a9d9655cc5aaa0dbaaf734aff6e47ec3054f6e8", + "marreq-core/src/repository/diesel_repo/baselines.rs": "1b636f5785c91ae8801608da4af8b5de2991f38edfd447f3e3285810035ddb75", + "marreq-core/src/repository/diesel_repo/comments.rs": "d2fe0edc50e95a4975a804ac68576bfee6166d91ae6d393b736a690a77f1162d", + "marreq-core/src/repository/diesel_repo/custom_fields.rs": "2c79f7da0dd3329712dc526d9f275ae3b5529b60ce2c5a38b3823c341aaaf7f9", + "marreq-core/src/repository/diesel_repo/groups.rs": "dcf3ecbbf2366a732fe72fc722afac9b4584772b58dbbaf7dc3903add4da75e6", + "marreq-core/src/repository/diesel_repo/idempotency.rs": "b8245707ed7c35683d461cc14ee8e8952bb654b63d9f87da2959ce2b371f9959", + "marreq-core/src/repository/diesel_repo/identities.rs": "29d8cba985744fb801f643585cd81f2d6e762bd60d2a748857dcdbed45d95769", + "marreq-core/src/repository/diesel_repo/logs.rs": "828817d0a2163df7b0d22176eadea4757731dd7ae4b6e88549b6fc68b0c848f2", + "marreq-core/src/repository/diesel_repo/lookups.rs": "35bfeba0d637674d912b97c72100ef58b09cf419eec25c368d7065a3354f491a", + "marreq-core/src/repository/diesel_repo/matrix.rs": "e7a75f4d2dfe71183ff0938c101130403343ecb21283f6c490d1543ab46d04e1", + "marreq-core/src/repository/diesel_repo/mod.rs": "3bef7d6ac3c6a21da409b8a9eec67d819f7ecf81ec340383f997148a11c76082", + "marreq-core/src/repository/diesel_repo/notifications.rs": "c49f845b11f9fa64e23a637a682919140133c761d2a9f049ebbfe0d3bc24b618", + "marreq-core/src/repository/diesel_repo/oauth.rs": "7c616112874226213fa2ddaf66cde5012d5c1398c279403ceb3e7ef506c99eba", + "marreq-core/src/repository/diesel_repo/projects.rs": "5f1a9a0081a7c6674e74e9bfe35cb3d0114e434bf97944453b41eeb1958fb1a0", + "marreq-core/src/repository/diesel_repo/report_templates.rs": "3d4542ced77375a9dc9e7066f288338d3dc042fcec5193c2b5181c140d470f64", + "marreq-core/src/repository/diesel_repo/requirement_links.rs": "6775b67d323497e444149a3c45c8ccec11d87a602add7d001ecec4963e88239b", + "marreq-core/src/repository/diesel_repo/requirements.rs": "24a0e00ddc48062949761b431dd46741ed8d261a0f228df2727c99708a85b75d", + "marreq-core/src/repository/diesel_repo/saved_views.rs": "6051322dcc26e2f6c194243460f3f292d12893035deb844b098a48cee980b9ca", + "marreq-core/src/repository/diesel_repo/use_cases.rs": "fcec4e43d0d65a467daa6abc5fcce97582ca71fc78c2f0ef4461b70094c3bfa1", + "marreq-core/src/repository/diesel_repo/users.rs": "883cf4fe2a69a882fbf71b37ddbed93191cb20910c2fc5d75d8c2aed7d92197d", + "marreq-core/src/repository/diesel_repo/verification_control.rs": "29c63e62ab0a445eaf11e28756a301d1ede358005eeaf12fc95769e4e5911f90", + "marreq-core/src/repository/diesel_repo/verifications.rs": "4493d652fd759c63e6220e8b2ef17a08e547283180da82aba189b03f9a9caa9c", + "marreq-core/src/repository/diesel_repo/workspaces.rs": "4ebe8759cc12391bc6b0b7679f1bfba462518c467aaf5576f50e777d338d7622", + "marreq-core/src/repository/diesel_repo_mock.rs": "2af62bd62ae078aafff93e01e7be20096e5b86eec28e034f0b81f2673e348e37", + "marreq-core/src/repository/errors.rs": "9cf994d8f428567cd8f0ca5206ba0b363e555e4dfb018561968e2cc7081dce3c", + "marreq-core/src/repository/mod.rs": "13f6e6696619adef56e4b541399be903837c7b6a31df20f7d552055fe1694dde", + "marreq-core/src/repository/tests.rs": "fe1dd6ba26d7ebf588d4fe16b33d030303af5875d3e2017d30154125bfe84413", + "marreq-core/src/reqif/archive.rs": "0bb0e69455933ae03303ddcd53c50573aad11f176e603bdd32e1db28cb573d36", + "marreq-core/src/reqif/export.rs": "66cf5c01957bf4eba237b59c0cfb7863e9ae35d0648f3bd87a1668263a0234cd", + "marreq-core/src/reqif/import.rs": "0dbbdfac7dfe711840d1e83a38bec5b74334ef89da844928f9b0cc6207d6666e", + "marreq-core/src/reqif/mapping.rs": "1cb83d2f18e1c6161c05680d22ef16dfa055531519043247ff2685e8ea122189", + "marreq-core/src/reqif/mod.rs": "48a98c711cb01e94f40dd29a7b7f9abcf02d42e601f436e4961247c3aa4bba1a", + "marreq-core/src/reqif/schema.rs": "fc5601a2d63f35312efa437e19b14dfa197ab0bb0b4c38e731b52c0683ad0ff3", + "marreq-core/src/reqif/vendor_import_tests.rs": "bf74ae53531b3a6ad1f725e26be528b6f3332a00127c3e3edf924a3843bbaec4", + "marreq-core/src/rich_text.rs": "ff4e2c4f60e15f52e933fb6226cecf193f4bac9c47d45b1a0e2158d28751936e", + "marreq-core/src/routes/api_info.rs": "1994ccb506523391848cc2b5202bffffff5362c9f3e3937c833a4d07944f7467", + "marreq-core/src/routes/catchers.rs": "ed18bab7d2bc2bcfd36ca96a49fe1071acb08747d09cf010b01d53dc3db5e65e", + "marreq-core/src/routes/helpers.rs": "396527d619b65b32880b59d5f75b195372d2396164536d6b8224bbb78103a3ea", + "marreq-core/src/routes/mod.rs": "c25096024e4b6fd5c63ec7e0b658a34bb3faabad2b6a5c08825b37af1909c2a2", + "marreq-core/src/saved_view_definition.rs": "a222e3e907f7ed5dacc759961eba299389eae4bd79387b8370772b6a526da4c3", + "marreq-core/src/schema.rs": "4a8a5e2aa8a44ad974e1cb0737ebf609f80f0c729c103ec5476adb039f98f536", + "marreq-core/src/services/applicability_service.rs": "da755f23a963182b284686c1d4987356449e96764780646b33b80e804cf7e719", + "marreq-core/src/services/attachment_service.rs": "ba06b3dde76eca6730fe17db100b335d03daac762f953efffe8969af274b7338", + "marreq-core/src/services/base_service.rs": "e649209878f6920588beebca68188171e423bbadec50a5e8a3ead9d16ce0ad1c", + "marreq-core/src/services/baseline_service.rs": "726dbce5864593256f856cdbcdf4a3ce37faf627642cbb5968fb82438a767a07", + "marreq-core/src/services/cache_service.rs": "8a94395ef50b6b369cd935f3614e6aacdb24a4ad1e1237581214122afa9acb7a", + "marreq-core/src/services/category_service.rs": "9c5c14bfe03c398b2dde0d22aee7567c6dc5ef325c66d8598be7e46a6441b35c", + "marreq-core/src/services/comment_service.rs": "b7ead06f324840fea8c0ab35812b45c9a954d5a72c44fee340331c86b647db02", + "marreq-core/src/services/config_authorization_tests.rs": "b1ec6c9fbdf28769258f8d23c8aaaf1e4dbba62621c97e5b037b03ce1642d4ce", + "marreq-core/src/services/custom_field_service.rs": "7778d7e2a95560e608efe5ca4dd33353cf73d30a125f7eb7a679b38a24d295ec", + "marreq-core/src/services/decorated_requirement_service.rs": "e7a04e3beccbe151b1d936b53cbbd2df2d9a0405f649d6d02e868d6e842815b3", + "marreq-core/src/services/decorated_test_service.rs": "37c82e2843540b78367f7337988d347cedb03b25e7005edcb0b45972c07d4bd6", + "marreq-core/src/services/dsm_service.rs": "9409bdb79b7269305a04381cf77d6d8329de8fec54d033766d6cb344504331ed", + "marreq-core/src/services/email_sender.rs": "1a556590e3b034839fde6adc634dd563ca93a1642af070c5646b148690cf5a73", + "marreq-core/src/services/external_auth_service.rs": "1a03d40e81701a094f16b1010c1d90af5246d565f8ce35b5ecabb654a81b2dfe", + "marreq-core/src/services/group_service.rs": "bdb286fead51cde5d91a669f6f3aca77a1cabc86ef1389e287b3d193cebd3e90", + "marreq-core/src/services/log_service.rs": "b63aee03c8fde42e9143c2f4f19e7eb5df37a1299380829d093c0e44dcde5e5c", + "marreq-core/src/services/matrix_service.rs": "57ace88167fccefe005e4dabb852a44d4c333107308208f0bec6e7bfc82cd1bf", + "marreq-core/src/services/mentions.rs": "37338f744e659c1424409c3375e71fdc8df51c44a2ee67815f0b293383198483", + "marreq-core/src/services/mod.rs": "419b0ffb22da94c32ae983a7a1fd9cfe83cf8c6066d995d2afbebdab378f2af9", + "marreq-core/src/services/notification_service.rs": "959bf356384407505d9c13158072aea84767d32834b807c681667db6ab3bd575", + "marreq-core/src/services/project_service.rs": "cb0ba576c9e4a33700e99a07c2d57d9ed26f4be54f2c700a2c55380e402c8a6d", + "marreq-core/src/services/reqif_service.rs": "1c4134dbaeacb09b259507159528a593acaf36a0a182ec7ba6aa4aefbec92f24", + "marreq-core/src/services/reqifz_service.rs": "f838756002594afbedbc94346e1c143394b2e63c2fd1d071f3de49db972b2e31", + "marreq-core/src/services/requirement_analytics_service.rs": "1388c3f6b506802ff4fb9255e5047ad66b66701b2645750d460c647e287544f8", + "marreq-core/src/services/requirement_diff_service.rs": "33cd9c92855de59df501e8343b508e316275c03852703012967bfe74b53ea2d9", + "marreq-core/src/services/requirement_service.rs": "3e8d9dc89253780582981b3086094e66dfb69aeb329575ad810a68e32e57e084", + "marreq-core/src/services/saved_view_service.rs": "0c8376fdd9fc2d6eb402382a3338cac73f0507e1fb1ba1c64b305ec9a4bb778f", + "marreq-core/src/services/semantic_search/config.rs": "5203461fa16be63887069fb6ebf3ed0833096ee6823f050ffa73d93ca4300198", + "marreq-core/src/services/semantic_search/document_builder.rs": "81f6f8d224d5595eb6e57bc3604ecfc338bb634fca682cb5bc43e7d48332d52f", + "marreq-core/src/services/semantic_search/embedding_provider.rs": "3f8e5d3e66bf78ae3216cadc76ab4e4c7480226830abba4a2e722268fbd765a4", + "marreq-core/src/services/semantic_search/indexing_service.rs": "8ea2d41ab9a64a8958e78163ab76d1a960220a1f5786077f1c75cc6c69d62a08", + "marreq-core/src/services/semantic_search/llm_provider.rs": "b7318a4e6e8ae2e794a51a2d644d88cd6a6674b1de4fca7098d83cf85ffe4fa3", + "marreq-core/src/services/semantic_search/mod.rs": "2cf968e31b72125552698ad1beebd8648b2cc628410c3aff0046303470fd6200", + "marreq-core/src/services/semantic_search/search_service.rs": "3603273c894ddb0d2f2980ab8b6290f70664c3d313011ba0a082ea0f2a393c54", + "marreq-core/src/services/status_service.rs": "b85e510fc9deffa37c51106d4d56d033aba5f8499cd7a53fc94452708e081b23", + "marreq-core/src/services/tests.rs": "244cb904f099f380754efab68ed8632a350ed5c27b333670e7d057705004bcaa", + "marreq-core/src/services/use_case_service.rs": "0e1e3f42e63b5990389bc62ee9765dc3bd71d619908897d5ad036a52f85b60b4", + "marreq-core/src/services/user_provisioning_service.rs": "ed4a1d192b3a40e66f689f80730985cb9acb6e88bc6cb6307c875103257a42d9", + "marreq-core/src/services/user_service.rs": "855663cc391eda53582ca2bf8160bdbe5dfe6e803e257a5b377c7d2b6d2acb2d", + "marreq-core/src/services/verification_control_service.rs": "b2015f9051fdcbfabfec9330a890ba303feb55c252df6143c49a0cde4310fee7", + "marreq-core/src/services/verification_diff_service.rs": "63f1bdb62d3e1092bf45a5907fe3a393707e832f390bdc839dc7aae4c1804308", + "marreq-core/src/services/verification_method_service.rs": "8491d252d76cbf3f20fa20c2850e45d962ca2a55715247a8b25b781ae03d4a66", + "marreq-core/src/services/verification_service.rs": "75eb4a790eb92dd866e91037d6d4f8ccb3db80b61958014d5d2a3751e4ec10f2", + "marreq-core/src/status_enums.rs": "0b3794e640c934f10584780bb703cbd8dd54bf1fd218841c70e5975ee85e20a3", + "marreq-core/src/storage/content_type.rs": "e324695785b60c63fc14a76d53d4ab5a1891fb623ea6485502f92cb892f6c423", + "marreq-core/src/storage/local.rs": "112afb565a8eff8007ee499baaf5751a96dec78df5805b23d40effc8ce099801", + "marreq-core/src/storage/mod.rs": "79653093398c728e8a2be8270e0ccadcc14bf3c8ac8dbce62d0fe344c42de66b", + "marreq-core/src/use_case_document/diff.rs": "9059f723d9f14704f80e1901be32483d0c3de552dd61f8a45d5dcc12e5032dd9", + "marreq-core/src/use_case_document/mod.rs": "d27cff5c38cfc5a88a47b3e8615f7bf2ce19b11345b603dbd6e46fff125e72e7", + "marreq-core/src/use_case_document/operations.rs": "78ab1e3750a7ace36991660e23bc5fc9e05e88de2452d90ecbd4d7fa8095595c", + "marreq-core/src/validation/error.rs": "e80f3f225c3300a9e49f78c2a07ebbe66fb0ce7237e1010f8f51341380f0805d", + "marreq-core/src/validation/mod.rs": "41643b71b016e8a1d07384979ec9cd54c5ed860ab9a8a78326485251638f3cd0", + "marreq-core/src/validation/tests.rs": "589456901adce6f94a66db2b3ee33ea01f1db596a9be51680abdeef8b05fed1b", + "marreq-server/src/api/mod.rs": "5707e1cbc3c10b9e33e62bc7b3b244a7a565024a1c7147562634c0da7b1d3331", + "marreq-server/src/deployment.rs": "840d3a3324402d64a2448d888ea2f569656f2fc07c8d38dfc75b862b0e2714d9", + "marreq-server/src/main.rs": "b7c82e45b16873d46e9a4c178e1311d62b2d7a90a15d4e0d39a6848fe3c859b6", + "marreq-server/src/routes.rs": "348dbf2079a01db04f81209371c33c01e20ddab4ed4f1c8b34108e172c360058", + "mcp-server/compatibility.json": "0d9bcff76abe3a44b59c5bbcd3b73acd29266394e25c16b7c4b069bec28b9472", + "mcp-server/src/check-duplicates.ts": "ccdf3f47bb2d562a316b4d957cae8786f00c4255d1100e5f8bfb08202ae15d75", + "mcp-server/src/client.ts": "6011376ae4200afd83a8110d5c997df604791b3d058726b977e0270100c80306", + "mcp-server/src/context.ts": "7a0666e3f92ce56f55442425dbc1c917f1670665bd8c30fc363036d2479b09f4", + "mcp-server/src/credentials.ts": "e7697f096ccc60de4709e9b94c8bb0091c23fb090234ae4c2a36fc5f1011639c", + "mcp-server/src/gap-tools.ts": "c0453c6dac1ed6c6beb2460c47849ed5b6bf70e790b4ffc7b4fdc3f35b19f6a8", + "mcp-server/src/index.ts": "8ae7b012c087c4d522e688d1119b555d252ad2199553e8829b4445a73f46c272", + "mcp-server/src/pagination.ts": "64d0dcdec7dc6ba268f44745e281da0a2a52839804d328261be336686f8f5a5d", + "mcp-server/src/prompts.ts": "e80f45676aa1e29db25b1c637366a99c04bd0b7dd2aa402d050c000ea93ca698", + "mcp-server/src/remote.ts": "db6a0bee20bc73d3c6635d5364dc5a98e06a0b30aae5aa07482a664cef4d328b", + "mcp-server/src/resources.ts": "0c68424f0676391ecdf66df5ae8d78f026e4b82d8683e6fe0759ab33d6720fd6" +} diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index 79cdc045..4bb86309 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -212,3 +212,180 @@ A minimal **OpenAPI 3** sketch for auth and session project listing lives in [`o - **Adminer:** host port **8081** (to avoid clashing with the frontend). See [`../../docker/README.md`](../../docker/README.md). + +## P0 contract-hardening audit and current coverage (#423) + +**This is an incomplete bootstrap, not completion of the P0 prerequisite.** The +OpenAPI inventory lists all 245 compiled HTTP API operations: 241 on Server and +245 on Cloud, including origin-root OAuth routes. Currently 39 operations have +explicit wire schemas; 206 are marked `x-marreq-unverified` and are inventory +entries only. The release completeness gate deliberately rejects them. A passing +OpenAPI validator or PR check does **not** establish full API compatibility. + +Architecture assessment: + +| Boundary | Implementation and observations | +| --- | --- | +| Shared backend | `marreq-core/src/app.rs::declared_mounts` composes policy-classified API and root OAuth routes. Rocket guards enforce sessions, Bearer scopes, membership and permissions. Diesel models are not automatically wire schemas: flattened DTOs, computed JSON, omitted fields and custom responders matter. | +| Server | `marreq-server/src/routes.rs` currently adds no endpoints. User creation/admin promotion and database backup are shared endpoints with deployment-mode restrictions, not absent Server/Cloud routes. | +| Cloud | Four additional public account endpoints: register, verify-email, forgot-password and reset-password. Their presence differs from Server; shared capabilities also differ at runtime. | +| Frontend | `frontend/src/api/*.ts`, plus direct fetches in pages/components. Session credentials and CSRF are essential; binary downloads, multipart imports and redirects cannot be modeled as ordinary JSON. Only metadata/provider discovery and selected use-case response models have migrated to generated types so far. | +| MCP | `mcp-server/src/client.ts` wraps project-scoped REST calls; `index.ts` defines tools, `remote.ts` owns MCP HTTP sessions and delegated credentials. Principal identity has nullable `client_id`/`grant_id`, even though the previous TypeScript definitions described optional strings/numbers. The Node MCP transport and `/healthz` are outside the Rust HTTP API. | +| Authentication | `/api/home/attention` now accepts `ProjectsRead` scoped credentials; the older session-only description above is stale. `/api/search` remains session-only. `/oauth/*` and `/.well-known/*` are mounted at the origin root. Guard/fairing responses can be empty or framework-generated; consumers must not assume every error is an `ApiError` JSON object. | +| Release | Existing component tags remain the publishing triggers. Independent artifact versions are allowed. Current `main` is never substituted for an unavailable historical contract. No deployment or global release migration is implemented here. | + +The explicitly modeled surface includes public build/deployment/CSRF/provider +metadata, current user, MCP principal, the four Cloud account routes, three OAuth +discovery routes, and all 25 use-case operations (including draft operations, +publishing, versions, approvals, comments, links, baseline content and diffs). +Use-case document requests distinguish omitted defaults from response properties +that Serde always emits. Unmodeled inventory entries do not promise fabricated +response bodies/status codes. Origin-root UI/HTML and internal CSRF-denial routes +are excluded from the API inventory comparison. + +### Source of truth and change workflow + +`openapi.yaml` is the wire contract. This retains Rocket/Diesel and introduces no +backend runtime dependency. `api-contract-routes.json` and both +`generated-contract.ts` files are reproducible derivatives, not independently +maintained contracts. The source-review digest records which producer/consumer +sources were audited. It detects changes requiring review, but **does not prove +serialization correctness** and is not a replacement for integration tests. + +When changing an endpoint: + +1. Inspect the actual Rocket mount, guards, Serde serialization/default/flatten/ + skip behavior, response status, and both consumers. Update OpenAPI accordingly. +2. Add or extend producer exchanges and consumer tests. Remove + `x-marreq-unverified` only after auditing the complete operation, not merely + because its route exists. Preserve unknown error-body handling. +3. Regenerate derived artifacts and explicitly record the source review: + + ```sh + python3 scripts/contracts/generate.py + python3 scripts/contracts/check.py --record-source-review + python3 scripts/contracts/check.py + python3 -m unittest discover -s scripts/contracts -v + ``` + +4. Run the compiled route check and isolated real-backend checks: + + ```sh + cargo test --locked -p marreq-cloud --test api_contract_inventory + npm ci --prefix frontend + npm ci --prefix mcp-server + scripts/contracts/test-backend.sh + ``` + +Install Python tooling with `python3 -m pip install -r +scripts/contracts/requirements.txt` in a virtual environment. Backend builds need +`libpq-dev`; Docker and Node 22 are needed for the isolated smoke check. The script +creates a new loopback-only PostgreSQL container with tmpfs storage, applies +migrations, seeds synthetic OAuth credentials through the integration test, and +starts a real `marreq-server` process. It exercises the actual frontend transport +and built MCP client over HTTP. It records requests/responses, never Authorization +headers or cookies. It ignores existing database URLs when creating its test DB; +it never connects to a production database. Cleanup runs on exit. Missing/skipped +critical producer exchanges fail transcript validation. + +### Remaining risks before P0 can be accepted + +- Audit the remaining **206** operations: authentication protocol/session flows, + projects/groups, requirements/verifications, traceability, matrices, baselines, + catalogs, administration, search, imports, exports, reports and attachments. + Presence in the inventory is not schema coverage. +- Complete per-consumer operation selection and generated request/response type + adoption, including direct frontend fetches. Bundles currently conservatively + require the entire backend surface; this can reject unrelated changes. +- Add exhaustive consumer request checks and live Cloud/session/OAuth protocol + coverage. Current executable producer/consumer coverage is representative, + not exhaustive, and no historical release is retrospectively certified. +- Retain verified bundles for real released artifact versions and bootstrap an + accurate deployment plan. Versions in package manifests alone are insufficient. +- Close existing test/security failures before making this a release-ready change. + +Until these are resolved, leave the PR in draft and keep publication blocked by +`check.py --require-complete`. Do not remove that gate to make a release green. + +### Implementation validation record (2026-10-27) + +All npm installs used the committed lockfiles (`npm ci` at root, in `frontend`, +and in `mcp-server`). Rust commands on the development host used temporary +`LIBRARY_PATH`/`LD_LIBRARY_PATH` settings pointing to an extracted `libpq` package; +no system/production database configuration was changed. + +| Exact command | Result | +| --- | --- | +| `python3 scripts/contracts/check.py` | PASS; 245 inventoried operations, 39 modeled operations, 206 explicit gaps | +| `python3 scripts/contracts/generate.py --check` | PASS; generated types and compiled-route expectations reproducible | +| `python3 -m unittest discover -s scripts/contracts -v` | PASS; 39 positive/negative tests, including historical plans and process exit codes | +| `python3 scripts/contracts/check.py --require-complete` | EXPECTED FAIL (exit 1); incomplete producer contract plus 206 unverified operations | +| `python3 scripts/contracts/compatibility.py --manifest /tmp/marreq-nonexistent-plan.json` | EXPECTED FAIL (exit 1); structured missing-evidence error | +| `cargo test --locked -p marreq-cloud --test api_contract_inventory` | PASS; compiled Server and Cloud routes match derived expectations | +| `scripts/contracts/test-backend.sh` | PASS; disposable migrated PostgreSQL, production Rocket handlers, real Server HTTP process and actual frontend/MCP clients; 15 validated exchanges, 33 explicitly inventory-only exchanges, three corrupted-traffic negative controls rejected | +| `cargo test --workspace` | PASS; 2,061 passed, one ignored. Default workspace feature unification enables test helpers; DB-specific tests without explicit test DB may skip internally. The separate isolated production-path run above is required evidence. | +| `cargo fmt --all -- --check` | PASS | +| `cargo clippy --workspace --all-targets -- -D warnings` | PASS | +| `npm run build --prefix frontend` | PASS; existing large-chunk warning | +| `npm run build --prefix mcp-server` | PASS | +| `npm test --prefix mcp-server` | PASS; 51 tests | +| `npm run test:coverage --prefix frontend` | PASS; 108 suites, 704 tests, 70.36% line coverage. An earlier ordinary full run had one report-builder failure; its isolated rerun and full coverage rerun passed. | +| `npx vitest run marreq-core/tests/js` | PASS; 411 tests, seven skipped | +| `npx --no-install stylelint 'frontend/static/**/*.css' --config .stylelintrc.json` | PASS | +| `npm run check:unused-css` | PASS (exit 0); existing warning reports 379 unused selectors | +| `npm audit --prefix frontend --omit=dev --audit-level=high` | PASS; no production vulnerabilities reported | +| `npm audit --prefix mcp-server --omit=dev --audit-level=high` | FAIL; existing lockfile reports one high and three moderate advisories | +| `cargo deny check advisories` | FAIL; existing locked dependencies trigger RUSTSEC-2026-0384, RUSTSEC-2026-0385 and RUSTSEC-2026-0387, plus yanked-crate warnings | +| `bash -n scripts/check-api-compatibility.sh scripts/contracts/test-backend.sh` | PASS | +| `git diff --check` | PASS | + +Dependency/security failures are reported, not suppressed or allowlisted. Their +remediation remains necessary before release readiness. The source-review digest +is deliberately conservative: backend service/model/guard changes and direct +frontend calls outside API modules require renewed review as well. This does not +replace the remaining serialization/consumer verification work. + +Changed files in this draft implementation (including generated artifacts): + +- `.github/workflows/api-contracts.yml` +- `.github/workflows/marreq-ci.yml` +- `.github/workflows/release-artifacts.yml` +- `.gitignore` +- `Cargo.lock` +- `docker/Dockerfile` +- `docker/frontend/Dockerfile` +- `docs/developer/api-contract-routes.json` +- `docs/developer/api-contract-source-review.json` +- `docs/developer/http-api-contract.md` +- `docs/developer/mcp-setup.md` +- `docs/developer/openapi.yaml` +- `docs/developer/use-cases-api.md` +- `docs/releases.md` +- `frontend/src/api/generated-contract.ts` +- `frontend/src/api/types.ts` +- `frontend/src/hooks/__tests__/useBuildInfo.test.ts` +- `frontend/src/pages/__tests__/AccountPage.test.tsx` +- `frontend/src/pages/__tests__/AdminPage.test.tsx` +- `frontend/src/pages/__tests__/HelpPage.test.tsx` +- `frontend/src/pages/__tests__/LoginPage.test.tsx` +- `marreq-cloud/Cargo.toml` +- `marreq-cloud/tests/api_contract_inventory.rs` +- `marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs` +- `mcp-server/Dockerfile` +- `mcp-server/compatibility.json` +- `mcp-server/src/client.ts` +- `mcp-server/src/generated-contract.ts` +- `mcp-server/src/remote.ts` +- `scripts/check-api-compatibility.sh` +- `scripts/contracts/bundle.py` +- `scripts/contracts/check.py` +- `scripts/contracts/check_baseline.py` +- `scripts/contracts/compatibility.py` +- `scripts/contracts/generate.py` +- `scripts/contracts/inventory.py` +- `scripts/contracts/requirements.txt` +- `scripts/contracts/smoke-consumers.mjs` +- `scripts/contracts/test-backend.sh` +- `scripts/contracts/test_compatibility.py` +- `scripts/contracts/test_contract_tooling.py` +- `scripts/contracts/verify_transcript.py` diff --git a/docs/developer/mcp-setup.md b/docs/developer/mcp-setup.md index 7210b7c5..1cc120ba 100644 --- a/docs/developer/mcp-setup.md +++ b/docs/developer/mcp-setup.md @@ -359,3 +359,15 @@ Reference: shared route list in `marreq-core/src/api/mod.rs` (plus deployment-sp - **Forbidden (403)**: The tokenโ€™s project scope (if set) must match `MARREQ_PROJECT_ID`; or the user must be a member of the project with permission for the action. - **Connection refused**: Ensure Marreq is running and `MARREQ_BASE_URL` is correct (e.g. `http://localhost:8000`). - **Tool not found**: Confirm `MARREQ_MODE` and `MARREQ_TRACE_WRITE` โ€” extended and trace tools are only registered when those settings enable them. + +## Backend compatibility metadata + +`mcp-server/compatibility.json` records the build-time supported backend range, +independently of the MCP component's version. It is retained in the MCP image. +CI validates its shape and release verification combines it with the exact +released MCP contract bundle; ranges alone do not establish compatibility. +See [release contract verification](../releases.md#api-compatibility-gate-and-retained-artifact-contracts-423-p0). +The current bootstrap still has unverified API operations and intentionally cannot +certify an independently deployed production combination. Existing mocked MCP +unit tests remain; `scripts/contracts/test-backend.sh` additionally executes the +actual client against a real loopback Rocket server and an isolated database. diff --git a/docs/developer/openapi.yaml b/docs/developer/openapi.yaml index a01ccfde..932e8797 100644 --- a/docs/developer/openapi.yaml +++ b/docs/developer/openapi.yaml @@ -1,216 +1,6989 @@ -openapi: 3.0.3 -info: - title: Marreq API (partial contract) - description: | - Subset of the Marreq JSON API for interchangeable frontends. - All application routes are under `/api`. Many more paths exist; see `marreq-core/src/api/mod.rs` and README. - version: 0.1.0 +openapi: 3.1.0 +info: {title: Marreq HTTP API, version: 0.1.0, description: Rocket route inventory plus explicitly audited wire + schemas. Operations marked x-marreq-unverified are NOT complete contracts and cause release verification to + fail closed. Root OAuth paths are distinct from /api. The Node MCP transport is not a Rust REST endpoint. See + http-api-contract.md for scope and remaining work.} servers: - - url: /api - description: Same-origin (nginx proxy or Vite dev proxy) - - url: http://localhost:8000/api - description: Direct Rocket (local) - +- {url: /} paths: - /auth/providers: + /api/projects/{project_id}/requirements/{id}/activity: get: - summary: Discover enabled browser authentication methods - operationId: authProviders + operationId: activity_requirement_activity_by_project + tags: [activity] + summary: activity / requirement activity by project + x-marreq-variants: [server, cloud] + x-marreq-handler: activity::requirement_activity_by_project + x-marreq-source: marreq-core/src/api/activity.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verifications/{id}/activity: + get: + operationId: activity_verification_activity_by_project + tags: [activity] + summary: activity / verification activity by project + x-marreq-variants: [server, cloud] + x-marreq-handler: activity::verification_activity_by_project + x-marreq-source: marreq-core/src/api/activity.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/applicability: + get: + operationId: applicability_list + tags: [applicability] + summary: applicability / list + x-marreq-variants: [server, cloud] + x-marreq-handler: applicability::list + x-marreq-source: marreq-core/src/api/applicability.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: applicability_create + tags: [applicability] + summary: applicability / create + x-marreq-variants: [server, cloud] + x-marreq-handler: applicability::create + x-marreq-source: marreq-core/src/api/applicability.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/applicability/{id}: + get: + operationId: applicability_get + tags: [applicability] + summary: applicability / get + x-marreq-variants: [server, cloud] + x-marreq-handler: applicability::get + x-marreq-source: marreq-core/src/api/applicability.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + put: + operationId: applicability_update + tags: [applicability] + summary: applicability / update + x-marreq-variants: [server, cloud] + x-marreq-handler: applicability::update + x-marreq-source: marreq-core/src/api/applicability.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: applicability_delete + tags: [applicability] + summary: applicability / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: applicability::delete + x-marreq-source: marreq-core/src/api/applicability.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/attachments: + get: + operationId: attachments_list + tags: [attachments] + summary: attachments / list + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::list + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectRequirementsAndVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: attachments_upload + tags: [attachments] + summary: attachments / upload + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::upload + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/attachments/{attachment_id}/download: + get: + operationId: attachments_download + tags: [attachments] + summary: attachments / download + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::download + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/attachments/{attachment_id}: + delete: + operationId: attachments_delete + tags: [attachments] + summary: attachments / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::delete + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/storage: + get: + operationId: attachments_get_storage + tags: [attachments] + summary: attachments / get storage + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::get_storage + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/storage/quota: + put: + operationId: attachments_set_quota + tags: [attachments] + summary: attachments / set quota + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::set_quota + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [AdminOnly] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/attachments: + get: + operationId: attachments_list_for_baseline + tags: [attachments] + summary: attachments / list for baseline + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::list_for_baseline + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: + get: + operationId: attachments_download_for_baseline + tags: [attachments] + summary: attachments / download for baseline + x-marreq-variants: [server, cloud] + x-marreq-handler: attachments::download_for_baseline + x-marreq-source: marreq-core/src/api/attachments.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/auth/providers: + get: + operationId: auth_auth_providers + tags: [auth] + summary: auth / auth providers + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_providers + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [] + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/external_ProviderDiscovery_response'} + /api/auth/external/{provider}/start: + get: + operationId: auth_auth_external_start + tags: [auth] + summary: auth / auth external start + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_external_start + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [] + parameters: + - in: path + name: provider + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /api/auth/external/{provider}/link: + post: + operationId: auth_auth_external_link_start + tags: [auth] + summary: auth / auth external link start + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_external_link_start + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: + - in: path + name: provider + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/external/{provider}/callback: + get: + operationId: auth_auth_external_callback + tags: [auth] + summary: auth / auth external callback + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_external_callback + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: + - in: path + name: provider + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /api/auth/csrf: + get: + operationId: auth_auth_csrf + tags: [auth] + summary: auth / auth csrf + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_csrf + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [] + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + csrf_token: &id005 {type: string} + required: [csrf_token] + additionalProperties: true + /api/auth/login: + post: + operationId: auth_auth_login + tags: [auth] + summary: auth / auth login + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_login + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /api/auth/identities: + get: + operationId: auth_auth_identities + tags: [auth] + summary: auth / auth identities + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_identities + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/identities/{identity_id}: + delete: + operationId: auth_auth_identity_delete + tags: [auth] + summary: auth / auth identity delete + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_identity_delete + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: + - in: path + name: identity_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/logout: + post: + operationId: auth_auth_logout + tags: [auth] + summary: auth / auth logout + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_logout + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/change-password: + post: + operationId: auth_auth_change_password + tags: [auth] + summary: auth / auth change password + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_change_password + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/me: + get: + operationId: auth_auth_me + tags: [auth] + summary: auth / auth me + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_me + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + security: + - cookieAuth: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/entities_User_response'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': + description: API error + content: + application/json: + schema: {$ref: '#/components/schemas/ApiError'} + put: + operationId: auth_auth_update_me + tags: [auth] + summary: auth / auth update me + x-marreq-variants: [server, cloud] + x-marreq-handler: auth::auth_update_me + x-marreq-source: marreq-core/src/api/auth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/admin/backup/info: + get: + operationId: backup_info + tags: [backup] + summary: backup / info + x-marreq-variants: [server, cloud] + x-marreq-handler: backup::info + x-marreq-source: marreq-core/src/api/backup.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/admin/backup: + post: + operationId: backup_create + tags: [backup] + summary: backup / create + x-marreq-variants: [server, cloud] + x-marreq-handler: backup::create + x-marreq-source: marreq-core/src/api/backup.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/baselines: + get: + operationId: baselines_list + tags: [baselines] + summary: baselines / list + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::list + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: baselines_create + tags: [baselines] + summary: baselines / create + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::create + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}: + get: + operationId: baselines_get + tags: [baselines] + summary: baselines / get + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::get + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/requirements: + get: + operationId: baselines_get_requirements + tags: [baselines] + summary: baselines / get requirements + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::get_requirements + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/traceability: + get: + operationId: baselines_get_traceability + tags: [baselines] + summary: baselines / get traceability + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::get_traceability + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/verifications: + get: + operationId: baselines_get_verifications + tags: [baselines] + summary: baselines / get verifications + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::get_verifications + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/diff: + get: + operationId: baselines_diff_baselines + tags: [baselines] + summary: baselines / diff baselines + x-marreq-variants: [server, cloud] + x-marreq-handler: baselines::diff_baselines + x-marreq-source: marreq-core/src/api/baselines.rs + x-marreq-guards: [ProjectBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/cache/stats: + get: + operationId: cache_stats + tags: [cache] + summary: cache / stats + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::stats + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/clear: + post: + operationId: cache_clear + tags: [cache] + summary: cache / clear + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::clear + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/cleanup: + post: + operationId: cache_cleanup + tags: [cache] + summary: cache / cleanup + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::cleanup + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/performance: + get: + operationId: cache_performance + tags: [cache] + summary: cache / performance + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::performance + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/recommendations: + get: + operationId: cache_recommendations + tags: [cache] + summary: cache / recommendations + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::recommendations + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/reset-counters: + post: + operationId: cache_reset_counters + tags: [cache] + summary: cache / reset counters + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::reset_counters + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/cache/health: + get: + operationId: cache_health + tags: [cache] + summary: cache / health + x-marreq-variants: [server, cloud] + x-marreq-handler: cache::health + x-marreq-source: marreq-core/src/api/cache.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /api/projects/{project_id}/catalog: + get: + operationId: catalog_get + tags: [catalog] + summary: catalog / get + x-marreq-variants: [server, cloud] + x-marreq-handler: catalog::get + x-marreq-source: marreq-core/src/api/catalog.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/categories: + get: + operationId: categories_list + tags: [categories] + summary: categories / list + x-marreq-variants: [server, cloud] + x-marreq-handler: categories::list + x-marreq-source: marreq-core/src/api/categories.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: categories_create + tags: [categories] + summary: categories / create + x-marreq-variants: [server, cloud] + x-marreq-handler: categories::create + x-marreq-source: marreq-core/src/api/categories.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/categories/{id}: + get: + operationId: categories_get + tags: [categories] + summary: categories / get + x-marreq-variants: [server, cloud] + x-marreq-handler: categories::get + x-marreq-source: marreq-core/src/api/categories.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + put: + operationId: categories_update + tags: [categories] + summary: categories / update + x-marreq-variants: [server, cloud] + x-marreq-handler: categories::update + x-marreq-source: marreq-core/src/api/categories.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: categories_delete + tags: [categories] + summary: categories / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: categories::delete + x-marreq-source: marreq-core/src/api/categories.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{requirement_id}/comments: + get: + operationId: comments_list_by_project + tags: [comments] + summary: comments / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: comments::list_by_project + x-marreq-source: marreq-core/src/api/comments.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: comments_create_by_project + tags: [comments] + summary: comments / create by project + x-marreq-variants: [server, cloud] + x-marreq-handler: comments::create_by_project + x-marreq-source: marreq-core/src/api/comments.rs + x-marreq-guards: [ProjectRequirementsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{requirement_id}/comments: + get: + operationId: comments_list + tags: [comments] + summary: comments / list + x-marreq-variants: [server, cloud] + x-marreq-handler: comments::list + x-marreq-source: marreq-core/src/api/comments.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: comments_create + tags: [comments] + summary: comments / create + x-marreq-variants: [server, cloud] + x-marreq-handler: comments::create + x-marreq-source: marreq-core/src/api/comments.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/custom_fields: + get: + operationId: custom_fields_list_by_project + tags: [custom_fields] + summary: custom fields / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: custom_fields::list_by_project + x-marreq-source: marreq-core/src/api/custom_fields.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: custom_fields_create + tags: [custom_fields] + summary: custom fields / create + x-marreq-variants: [server, cloud] + x-marreq-handler: custom_fields::create + x-marreq-source: marreq-core/src/api/custom_fields.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/custom_fields/{field_id}: + get: + operationId: custom_fields_get + tags: [custom_fields] + summary: custom fields / get + x-marreq-variants: [server, cloud] + x-marreq-handler: custom_fields::get + x-marreq-source: marreq-core/src/api/custom_fields.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: field_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + put: + operationId: custom_fields_update + tags: [custom_fields] + summary: custom fields / update + x-marreq-variants: [server, cloud] + x-marreq-handler: custom_fields::update + x-marreq-source: marreq-core/src/api/custom_fields.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: field_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: custom_fields_delete + tags: [custom_fields] + summary: custom fields / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: custom_fields::delete + x-marreq-source: marreq-core/src/api/custom_fields.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: field_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/dashboard: + get: + operationId: dashboard_dashboard_json + tags: [dashboard] + summary: dashboard / dashboard json + x-marreq-variants: [server, cloud] + x-marreq-handler: dashboard::dashboard_json + x-marreq-source: marreq-core/src/api/dashboard.rs + x-marreq-guards: [OptionalSessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/dsm: + get: + operationId: dsm_get_dsm + tags: [dsm] + summary: dsm / get dsm + x-marreq-variants: [server, cloud] + x-marreq-handler: dsm::get_dsm + x-marreq-source: marreq-core/src/api/dsm.rs + x-marreq-guards: [ProjectRequirementsAndTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/requirements.xlsx: + get: + operationId: exports_export_requirements_xlsx + tags: [exports] + summary: exports / export requirements xlsx + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_requirements_xlsx + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/verifications.xlsx: + get: + operationId: exports_export_verifications_xlsx + tags: [exports] + summary: exports / export verifications xlsx + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_verifications_xlsx + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/matrix.xlsx: + get: + operationId: exports_export_matrix_xlsx + tags: [exports] + summary: exports / export matrix xlsx + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_matrix_xlsx + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/dsm.xlsx: + get: + operationId: exports_export_dsm_xlsx + tags: [exports] + summary: exports / export dsm xlsx + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_dsm_xlsx + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/matrix-links.xlsx: + get: + operationId: exports_export_matrix_links_xlsx + tags: [exports] + summary: exports / export matrix links xlsx + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_matrix_links_xlsx + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/requirements.pdf: + get: + operationId: exports_export_requirements_pdf + tags: [exports] + summary: exports / export requirements pdf + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_requirements_pdf + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/report.pdf: + get: + operationId: exports_export_report_pdf + tags: [exports] + summary: exports / export report pdf + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_report_pdf + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/requirements.reqif: + get: + operationId: exports_export_requirements_reqif + tags: [exports] + summary: exports / export requirements reqif + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_requirements_reqif + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/requirements.reqifz: + get: + operationId: exports_export_requirements_reqifz + tags: [exports] + summary: exports / export requirements reqifz + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_requirements_reqifz + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/baselines/{filename}: + get: + operationId: exports_export_baseline_reqif + tags: [exports] + summary: exports / export baseline reqif + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_baseline_reqif + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: filename + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/bundle.json: + get: + operationId: exports_export_project_bundle + tags: [exports] + summary: exports / export project bundle + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_project_bundle + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/exports/bundle.zip: + get: + operationId: exports_export_project_bundle_zip + tags: [exports] + summary: exports / export project bundle zip + x-marreq-variants: [server, cloud] + x-marreq-handler: exports::export_project_bundle_zip + x-marreq-source: marreq-core/src/api/exports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups: + get: + operationId: groups_list + tags: [groups] + summary: groups / list + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::list + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: groups_create + tags: [groups] + summary: groups / create + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::create + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups/creatable: + get: + operationId: groups_list_creatable + tags: [groups] + summary: groups / list creatable + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::list_creatable + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups/{group_id}: + get: + operationId: groups_get + tags: [groups] + summary: groups / get + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::get + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + patch: + operationId: groups_update + tags: [groups] + summary: groups / update + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::update + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: groups_delete + tags: [groups] + summary: groups / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::delete + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups/{group_id}/projects: + get: + operationId: groups_list_projects + tags: [groups] + summary: groups / list projects + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::list_projects + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups/{group_id}/members: + get: + operationId: groups_list_members + tags: [groups] + summary: groups / list members + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::list_members + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/groups/{group_id}/members/{user_id}: + put: + operationId: groups_set_member_role + tags: [groups] + summary: groups / set member role + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::set_member_role + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: groups_remove_member + tags: [groups] + summary: groups / remove member + x-marreq-variants: [server, cloud] + x-marreq-handler: groups::remove_member + x-marreq-source: marreq-core/src/api/groups.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/home/attention: + get: + operationId: home_attention + tags: [home] + summary: home / attention + x-marreq-variants: [server, cloud] + x-marreq-handler: home::attention + x-marreq-source: marreq-core/src/api/home.rs + x-marreq-guards: [ProjectsRead] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/search: + get: + operationId: home_search + tags: [home] + summary: home / search + x-marreq-variants: [server, cloud] + x-marreq-handler: home::search + x-marreq-source: marreq-core/src/api/home.rs + x-marreq-guards: [SessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/imports/excel/preview: + post: + operationId: imports_preview_excel + tags: [imports] + summary: imports / preview excel + x-marreq-variants: [server, cloud] + x-marreq-handler: imports::preview_excel + x-marreq-source: marreq-core/src/api/imports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/imports/excel: + post: + operationId: imports_commit_excel + tags: [imports] + summary: imports / commit excel + x-marreq-variants: [server, cloud] + x-marreq-handler: imports::commit_excel + x-marreq-source: marreq-core/src/api/imports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/imports/reqif: + post: + operationId: imports_commit_reqif + tags: [imports] + summary: imports / commit reqif + x-marreq-variants: [server, cloud] + x-marreq-handler: imports::commit_reqif + x-marreq-source: marreq-core/src/api/imports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/imports/bundle: + post: + operationId: imports_import_project_bundle + tags: [imports] + summary: imports / import project bundle + x-marreq-variants: [server, cloud] + x-marreq-handler: imports::import_project_bundle + x-marreq-source: marreq-core/src/api/imports.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/admin/logs: + get: + operationId: logs_list + tags: [logs] + summary: logs / list + x-marreq-variants: [server, cloud] + x-marreq-handler: logs::list + x-marreq-source: marreq-core/src/api/logs.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/admin/logs/stats: + get: + operationId: logs_stats + tags: [logs] + summary: logs / stats + x-marreq-variants: [server, cloud] + x-marreq-handler: logs::stats + x-marreq-source: marreq-core/src/api/logs.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/admin/logs/export.json: + get: + operationId: logs_export_json + tags: [logs] + summary: logs / export json + x-marreq-variants: [server, cloud] + x-marreq-handler: logs::export_json + x-marreq-source: marreq-core/src/api/logs.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/admin/logs/cleanup: + post: + operationId: logs_cleanup + tags: [logs] + summary: logs / cleanup + x-marreq-variants: [server, cloud] + x-marreq-handler: logs::cleanup + x-marreq-source: marreq-core/src/api/logs.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/matrix: + get: + operationId: matrix_list + tags: [matrix] + summary: matrix / list + x-marreq-variants: [server, cloud] + x-marreq-handler: matrix::list + x-marreq-source: marreq-core/src/api/matrix.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/matrix: + get: + operationId: matrix_list_by_project + tags: [matrix] + summary: matrix / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: matrix::list_by_project + x-marreq-source: marreq-core/src/api/matrix.rs + x-marreq-guards: [ProjectTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verifications/{verification_id}/matrix: + get: + operationId: matrix_get_verification_matrix + tags: [matrix] + summary: matrix / get verification matrix + x-marreq-variants: [server, cloud] + x-marreq-handler: matrix::get_verification_matrix + x-marreq-source: marreq-core/src/api/matrix.rs + x-marreq-guards: [ProjectTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + put: + operationId: matrix_put_verification_matrix + tags: [matrix] + summary: matrix / put verification matrix + x-marreq-variants: [server, cloud] + x-marreq-handler: matrix::put_verification_matrix + x-marreq-source: marreq-core/src/api/matrix.rs + x-marreq-guards: [ProjectTraceabilityWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/mcp/principal: + get: + operationId: mcp_principal + tags: [mcp] + summary: mcp / principal + x-marreq-variants: [server, cloud] + x-marreq-handler: mcp::principal + x-marreq-source: marreq-core/src/api/mcp.rs + x-marreq-guards: [McpPrincipalAuth] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/McpPrincipalResponse'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + /api/mcp/audit: + post: + operationId: mcp_audit + tags: [mcp] + summary: mcp / audit + x-marreq-variants: [server, cloud] + x-marreq-handler: mcp::audit + x-marreq-source: marreq-core/src/api/mcp.rs + x-marreq-guards: [StdioMcpAuditAuth] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - bearerAuth: [] + x-marreq-authentication-note: Personal API tokens only; delegated OAuth and sessions rejected. + /api/mcp/internal/audit: + post: + operationId: mcp_internal_audit + tags: [mcp] + summary: mcp / internal audit + x-marreq-variants: [server, cloud] + x-marreq-handler: mcp::internal_audit + x-marreq-source: marreq-core/src/api/mcp.rs + x-marreq-guards: [InternalMcpAudit, McpAuditAuth] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - mcpServiceAuth: [] + /api/projects/{project_id}/me/permissions: + get: + operationId: members_get_my_permissions + tags: [members] + summary: members / get my permissions + x-marreq-variants: [server, cloud] + x-marreq-handler: members::get_my_permissions + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectProjectsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/members: + get: + operationId: members_list_members + tags: [members] + summary: members / list members + x-marreq-variants: [server, cloud] + x-marreq-handler: members::list_members + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/members/{user_id}: + put: + operationId: members_set_member_role + tags: [members] + summary: members / set member role + x-marreq-variants: [server, cloud] + x-marreq-handler: members::set_member_role + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: members_remove_member + tags: [members] + summary: members / remove member + x-marreq-variants: [server, cloud] + x-marreq-handler: members::remove_member + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/reviewers: + get: + operationId: members_list_project_reviewers + tags: [members] + summary: members / list project reviewers + x-marreq-variants: [server, cloud] + x-marreq-handler: members::list_project_reviewers + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectProjectsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + put: + operationId: members_put_project_reviewers + tags: [members] + summary: members / put project reviewers + x-marreq-variants: [server, cloud] + x-marreq-handler: members::put_project_reviewers + x-marreq-source: marreq-core/src/api/members.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/: + get: + operationId: meta_api_root + tags: [meta] + summary: meta / api root + x-marreq-variants: [server, cloud] + x-marreq-handler: meta::api_root + x-marreq-source: marreq-core/src/api/meta.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/meta/health: + get: + operationId: meta_health + tags: [meta] + summary: meta / health + x-marreq-variants: [server, cloud] + x-marreq-handler: meta::health + x-marreq-source: marreq-core/src/api/meta.rs + x-marreq-guards: [] + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + status: {const: ok, type: string} + service: {const: marreq, type: string} + required: [status, service] + additionalProperties: true + /api/meta/deployment: + get: + operationId: meta_deployment_info + tags: [meta] + summary: meta / deployment info + x-marreq-variants: [server, cloud] + x-marreq-handler: meta::deployment_info + x-marreq-source: marreq-core/src/api/meta.rs + x-marreq-guards: [] + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/DeploymentInfo'} + /api/meta/build: + get: + operationId: meta_build_info + tags: [meta] + summary: meta / build info + x-marreq-variants: [server, cloud] + x-marreq-handler: meta::build_info + x-marreq-source: marreq-core/src/api/meta.rs + x-marreq-guards: [] + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/BuildInfo'} + /api/notifications: + get: + operationId: notifications_list + tags: [notifications] + summary: notifications / list + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::list + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/notifications/unread-count: + get: + operationId: notifications_unread_count + tags: [notifications] + summary: notifications / unread count + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::unread_count + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/notifications/{id}/read: + patch: + operationId: notifications_mark_read + tags: [notifications] + summary: notifications / mark read + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::mark_read + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/notifications/read-all: + post: + operationId: notifications_mark_all_read + tags: [notifications] + summary: notifications / mark all read + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::mark_all_read + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/notifications/preferences: + get: + operationId: notifications_get_preferences + tags: [notifications] + summary: notifications / get preferences + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::get_preferences + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/notifications/preferences/{project_id}: + put: + operationId: notifications_set_preference + tags: [notifications] + summary: notifications / set preference + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::set_preference + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: notifications_delete_preference + tags: [notifications] + summary: notifications / delete preference + x-marreq-variants: [server, cloud] + x-marreq-handler: notifications::delete_preference + x-marreq-source: marreq-core/src/api/notifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /.well-known/oauth-authorization-server: + get: + operationId: oauth_authorization_server_metadata + tags: [oauth] + summary: oauth / authorization server metadata + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::authorization_server_metadata + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + issuer: &id001 {type: string} + authorization_endpoint: *id001 + token_endpoint: *id001 + registration_endpoint: *id001 + response_types_supported: + type: array + items: *id001 + grant_types_supported: + type: array + items: *id001 + code_challenge_methods_supported: + type: array + items: *id001 + scopes_supported: + type: array + items: *id001 + token_endpoint_auth_methods_supported: + type: array + items: *id001 + authorization_response_iss_parameter_supported: {type: boolean} + required: [issuer, authorization_endpoint, token_endpoint, registration_endpoint, response_types_supported, + grant_types_supported, code_challenge_methods_supported, scopes_supported, token_endpoint_auth_methods_supported, + authorization_response_iss_parameter_supported] + additionalProperties: true + description: See implementation and http-api-contract.md for validation rules. + security: [] + /.well-known/oauth-protected-resource: + get: + operationId: oauth_protected_resource_metadata + tags: [oauth] + summary: oauth / protected resource metadata + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::protected_resource_metadata + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + resource: *id001 + authorization_servers: + type: array + items: *id001 + scopes_supported: + type: array + items: *id001 + bearer_methods_supported: + type: array + items: *id001 + required: [resource, authorization_servers, scopes_supported, bearer_methods_supported] + additionalProperties: true + description: See implementation and http-api-contract.md for validation rules. + security: [] + /.well-known/oauth-protected-resource/mcp: + get: + operationId: oauth_protected_resource_metadata_mcp + tags: [oauth] + summary: oauth / protected resource metadata mcp + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::protected_resource_metadata_mcp + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + resource: *id001 + authorization_servers: + type: array + items: *id001 + scopes_supported: + type: array + items: *id001 + bearer_methods_supported: + type: array + items: *id001 + required: [resource, authorization_servers, scopes_supported, bearer_methods_supported] + additionalProperties: true + description: See implementation and http-api-contract.md for validation rules. + security: [] + /oauth/register: + post: + operationId: oauth_register + tags: [oauth] + summary: oauth / register + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::register + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /oauth/authorize: + get: + operationId: oauth_authorize_page + tags: [oauth] + summary: oauth / authorize page + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::authorize_page + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [OptionalSessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + post: + operationId: oauth_authorize_decision + tags: [oauth] + summary: oauth / authorize decision + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::authorize_decision + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [SessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /oauth/token: + post: + operationId: oauth_token + tags: [oauth] + summary: oauth / token + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::token + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + /api/oauth/grants: + get: + operationId: oauth_grants + tags: [oauth] + summary: oauth / grants + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::grants + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [SessionUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/oauth/grants/{id}: + delete: + operationId: oauth_revoke_grant + tags: [oauth] + summary: oauth / revoke grant + x-marreq-variants: [server, cloud] + x-marreq-handler: oauth::revoke_grant + x-marreq-source: marreq-core/src/api/oauth.rs + x-marreq-guards: [SessionUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects: + post: + operationId: projects_create + tags: [projects] + summary: projects / create + x-marreq-variants: [server, cloud] + x-marreq-handler: projects::create + x-marreq-source: marreq-core/src/api/projects.rs + x-marreq-guards: [ApiUserOrBearer] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + get: + operationId: projects_session_list_for_session + tags: [projects_session] + summary: projects session / list for session + x-marreq-variants: [server, cloud] + x-marreq-handler: projects_session::list_for_session + x-marreq-source: marreq-core/src/api/projects_session.rs + x-marreq-guards: [ProjectsRead] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}: + patch: + operationId: projects_update + tags: [projects] + summary: projects / update + x-marreq-variants: [server, cloud] + x-marreq-handler: projects::update + x-marreq-source: marreq-core/src/api/projects.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: projects_delete + tags: [projects] + summary: projects / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: projects::delete + x-marreq-source: marreq-core/src/api/projects.rs + x-marreq-guards: [ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/archive: + post: + operationId: projects_archive + tags: [projects] + summary: projects / archive + x-marreq-variants: [server, cloud] + x-marreq-handler: projects::archive + x-marreq-source: marreq-core/src/api/projects.rs + x-marreq-guards: [ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/unarchive: + post: + operationId: projects_unarchive + tags: [projects] + summary: projects / unarchive + x-marreq-variants: [server, cloud] + x-marreq-handler: projects::unarchive + x-marreq-source: marreq-core/src/api/projects.rs + x-marreq-guards: [ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/project-from-path/{slug}: + get: + operationId: projects_session_project_from_path + tags: [projects_session] + summary: projects session / project from path + x-marreq-variants: [server, cloud] + x-marreq-handler: projects_session::project_from_path + x-marreq-source: marreq-core/src/api/projects_session.rs + x-marreq-guards: [OptionalSessionUser] + parameters: + - in: path + name: slug + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/reports/types: + get: + operationId: reports_list_types + tags: [reports] + summary: reports / list types + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::list_types + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/report_templates: + get: + operationId: reports_list_templates + tags: [reports] + summary: reports / list templates + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::list_templates + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: reports_create_template + tags: [reports] + summary: reports / create template + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::create_template + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/report_templates/{template_id}: + get: + operationId: reports_get_template + tags: [reports] + summary: reports / get template + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::get_template + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: template_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + patch: + operationId: reports_update_template + tags: [reports] + summary: reports / update template + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::update_template + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: template_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: reports_delete_template + tags: [reports] + summary: reports / delete template + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::delete_template + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: template_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/reports/{file}: + post: + operationId: reports_generate + tags: [reports] + summary: reports / generate + x-marreq-variants: [server, cloud] + x-marreq-handler: reports::generate + x-marreq-source: marreq-core/src/api/reports.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: file + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{req_id}/versions/{v1}/diff/{v2}: + get: + operationId: requirement_diff_diff_versions + tags: [requirement_diff] + summary: requirement diff / diff versions + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_diff::diff_versions + x-marreq-source: marreq-core/src/api/requirement_diff.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: v1 + required: true + schema: {type: integer} + - in: path + name: v2 + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: + get: + operationId: requirement_diff_diff_versions_by_project + tags: [requirement_diff] + summary: requirement diff / diff versions by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_diff::diff_versions_by_project + x-marreq-source: marreq-core/src/api/requirement_diff.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: v1 + required: true + schema: {type: integer} + - in: path + name: v2 + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: + get: + operationId: requirement_diff_diff_baseline_vs_current + tags: [requirement_diff] + summary: requirement diff / diff baseline vs current + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_diff::diff_baseline_vs_current + x-marreq-source: marreq-core/src/api/requirement_diff.rs + x-marreq-guards: [ProjectRequirementsAndBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirement-version-links: + post: + operationId: requirement_version_links_create + tags: [requirement_version_links] + summary: requirement version links / create + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_version_links::create + x-marreq-source: marreq-core/src/api/requirement_version_links.rs + x-marreq-guards: [ProjectTraceabilityWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + get: + operationId: requirement_version_links_list + tags: [requirement_version_links] + summary: requirement version links / list + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_version_links::list + x-marreq-source: marreq-core/src/api/requirement_version_links.rs + x-marreq-guards: [ProjectTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirement-version-links/{link_id}: + delete: + operationId: requirement_version_links_delete + tags: [requirement_version_links] + summary: requirement version links / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_version_links::delete + x-marreq-source: marreq-core/src/api/requirement_version_links.rs + x-marreq-guards: [ProjectTraceabilityWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirement-version-links/link-types: + get: + operationId: requirement_version_links_link_types + tags: [requirement_version_links] + summary: requirement version links / link types + x-marreq-variants: [server, cloud] + x-marreq-handler: requirement_version_links::link_types + x-marreq-source: marreq-core/src/api/requirement_version_links.rs + x-marreq-guards: [ProjectTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements: + get: + operationId: requirements_list + tags: [requirements] + summary: requirements / list + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::list + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: requirements_create + tags: [requirements] + summary: requirements / create + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::create + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements: + get: + operationId: requirements_list_by_project + tags: [requirements] + summary: requirements / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::list_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: requirements_create_by_project + tags: [requirements] + summary: requirements / create by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::create_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{id}: + get: + operationId: requirements_get + tags: [requirements] + summary: requirements / get + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: requirements_delete + tags: [requirements] + summary: requirements / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::delete + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + patch: + operationId: requirements_patch_requirement + tags: [requirements] + summary: requirements / patch requirement + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::patch_requirement + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{id}: + get: + operationId: requirements_get_by_project + tags: [requirements] + summary: requirements / get by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + patch: + operationId: requirements_patch_by_project + tags: [requirements] + summary: requirements / patch by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::patch_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{id}/versions: + get: + operationId: requirements_list_versions + tags: [requirements] + summary: requirements / list versions + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::list_versions + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{id}/versions: + get: + operationId: requirements_list_versions_by_project + tags: [requirements] + summary: requirements / list versions by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::list_versions_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{req_id}/versions/{version_id}: + get: + operationId: requirements_get_version + tags: [requirements] + summary: requirements / get version + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get_version + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: + get: + operationId: requirements_get_version_by_project + tags: [requirements] + summary: requirements / get version by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get_version_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{id}/impacted_tests: + get: + operationId: requirements_get_impacted_tests + tags: [requirements] + summary: requirements / get impacted tests + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get_impacted_tests + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{id}/impacted_tests: + get: + operationId: requirements_get_impacted_tests_by_project + tags: [requirements] + summary: requirements / get impacted tests by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::get_impacted_tests_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsAndVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/requirements/{req_id}/versions/{version_id}/approval: + put: + operationId: requirements_set_version_approval + tags: [requirements] + summary: requirements / set version approval + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::set_version_approval + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: + put: + operationId: requirements_set_version_approval_by_project + tags: [requirements] + summary: requirements / set version approval by project + x-marreq-variants: [server, cloud] + x-marreq-handler: requirements::set_version_approval_by_project + x-marreq-source: marreq-core/src/api/requirements.rs + x-marreq-guards: [ProjectRequirementsApprove] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/saved_views: + get: + operationId: saved_views_list + tags: [saved_views] + summary: saved views / list + x-marreq-variants: [server, cloud] + x-marreq-handler: saved_views::list + x-marreq-source: marreq-core/src/api/saved_views.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: saved_views_create + tags: [saved_views] + summary: saved views / create + x-marreq-variants: [server, cloud] + x-marreq-handler: saved_views::create + x-marreq-source: marreq-core/src/api/saved_views.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/saved_views/{view_id}: + get: + operationId: saved_views_get + tags: [saved_views] + summary: saved views / get + x-marreq-variants: [server, cloud] + x-marreq-handler: saved_views::get + x-marreq-source: marreq-core/src/api/saved_views.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + patch: + operationId: saved_views_update + tags: [saved_views] + summary: saved views / update + x-marreq-variants: [server, cloud] + x-marreq-handler: saved_views::update + x-marreq-source: marreq-core/src/api/saved_views.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: saved_views_delete + tags: [saved_views] + summary: saved views / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: saved_views::delete + x-marreq-source: marreq-core/src/api/saved_views.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/semantic_search: + get: + operationId: semantic_search_semantic_search + tags: [semantic_search] + summary: semantic search / semantic search + x-marreq-variants: [server, cloud] + x-marreq-handler: semantic_search::semantic_search + x-marreq-source: marreq-core/src/api/semantic_search.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/ask: + post: + operationId: semantic_search_ask + tags: [semantic_search] + summary: semantic search / ask + x-marreq-variants: [server, cloud] + x-marreq-handler: semantic_search::ask + x-marreq-source: marreq-core/src/api/semantic_search.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/reindex: + post: + operationId: semantic_search_reindex + tags: [semantic_search] + summary: semantic search / reindex + x-marreq-variants: [server, cloud] + x-marreq-handler: semantic_search::reindex + x-marreq-source: marreq-core/src/api/semantic_search.rs + x-marreq-guards: [AdminOnly, ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/index_status: + get: + operationId: semantic_search_index_status + tags: [semantic_search] + summary: semantic search / index status + x-marreq-variants: [server, cloud] + x-marreq-handler: semantic_search::index_status + x-marreq-source: marreq-core/src/api/semantic_search.rs + x-marreq-guards: [ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/semantic_search/status: + get: + operationId: semantic_search_search_status + tags: [semantic_search] + summary: semantic search / search status + x-marreq-variants: [server, cloud] + x-marreq-handler: semantic_search::search_status + x-marreq-source: marreq-core/src/api/semantic_search.rs + x-marreq-guards: [ProjectAccess] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/status: + get: + operationId: status_list_requirement_statuses + tags: [status] + summary: status / list requirement statuses + x-marreq-variants: [server, cloud] + x-marreq-handler: status::list_requirement_statuses + x-marreq-source: marreq-core/src/api/status.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: status_create_requirement_status + tags: [status] + summary: status / create requirement status + x-marreq-variants: [server, cloud] + x-marreq-handler: status::create_requirement_status + x-marreq-source: marreq-core/src/api/status.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/status/{id}: + get: + operationId: status_get_requirement_status + tags: [status] + summary: status / get requirement status + x-marreq-variants: [server, cloud] + x-marreq-handler: status::get_requirement_status + x-marreq-source: marreq-core/src/api/status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + put: + operationId: status_update_requirement_status + tags: [status] + summary: status / update requirement status + x-marreq-variants: [server, cloud] + x-marreq-handler: status::update_requirement_status + x-marreq-source: marreq-core/src/api/status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: status_delete_requirement_status + tags: [status] + summary: status / delete requirement status + x-marreq-variants: [server, cloud] + x-marreq-handler: status::delete_requirement_status + x-marreq-source: marreq-core/src/api/status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/requirements/{id}/trace_up: + get: + operationId: traceability_trace_up + tags: [traceability] + summary: traceability / trace up + x-marreq-variants: [server, cloud] + x-marreq-handler: traceability::trace_up + x-marreq-source: marreq-core/src/api/traceability.rs + x-marreq-guards: [ProjectRequirementsAndTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/{id}/trace_down: + get: + operationId: traceability_trace_down + tags: [traceability] + summary: traceability / trace down + x-marreq-variants: [server, cloud] + x-marreq-handler: traceability::trace_down + x-marreq-source: marreq-core/src/api/traceability.rs + x-marreq-guards: [ProjectRequirementsVerificationsAndTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/coverage_report: + get: + operationId: traceability_coverage_report + tags: [traceability] + summary: traceability / coverage report + x-marreq-variants: [server, cloud] + x-marreq-handler: traceability::coverage_report + x-marreq-source: marreq-core/src/api/traceability.rs + x-marreq-guards: [ProjectTraceabilityRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/traceability/clear_suspect: + post: + operationId: traceability_clear_suspect + tags: [traceability] + summary: traceability / clear suspect + x-marreq-variants: [server, cloud] + x-marreq-handler: traceability::clear_suspect + x-marreq-source: marreq-core/src/api/traceability.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/traceability/clear_suspect: + post: + operationId: traceability_clear_suspect_by_project + tags: [traceability] + summary: traceability / clear suspect by project + x-marreq-variants: [server, cloud] + x-marreq-handler: traceability::clear_suspect_by_project + x-marreq-source: marreq-core/src/api/traceability.rs + x-marreq-guards: [ProjectTraceabilityWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/use-cases: + get: + operationId: use_cases_list + tags: [use_cases] + summary: use cases / list + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::list + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: q + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} + - in: query + name: approval_state + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} + - in: query + name: actor + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} + - in: query + name: has_suspect + required: false + schema: + anyOf: + - {type: boolean} + - {type: 'null'} + - in: query + name: requirement_id + required: false + schema: + anyOf: + - {type: integer} + - {type: 'null'} + - in: query + name: verification_id + required: false + schema: + anyOf: + - {type: integer} + - {type: 'null'} + - in: query + name: sort_column + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} + - in: query + name: sort_dir + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_UseCaseRow_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + post: + operationId: use_cases_create + tags: [use_cases] + summary: use cases / create + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::create + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: &id002 {type: string} + description: Safe replay for the same principal, target, operation and payload; changed payload returns + 409. + responses: + '201': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_UseCaseDetail_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + description: Create the shared draft; optional reference_code, title, primary_actor and document. A missing + code is generated. Supports Idempotency-Key. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:write'] + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + reference_code: + type: [string, 'null'] + title: + type: [string, 'null'] + primary_actor: + type: [string, 'null'] + document: + anyOf: + - {$ref: '#/components/schemas/UseCaseDocument'} + - {type: 'null'} + additionalProperties: true + /api/projects/{project_id}/use-cases/next-code: + get: + operationId: use_cases_next_code + tags: [use_cases] + summary: use cases / next code + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::next_code + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_NextCode_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}: + get: + operationId: use_cases_get + tags: [use_cases] + summary: use cases / get + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::get + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: version_id + required: false + schema: + anyOf: + - {type: integer} + - {type: 'null'} + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_UseCaseDetail_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + delete: + operationId: use_cases_delete + tags: [use_cases] + summary: use cases / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::delete + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': {description: Deleted} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + x-marreq-bearer-scopes: ['use_cases:write'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/draft: + get: + operationId: use_cases_get_draft + tags: [use_cases] + summary: use cases / get draft + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::get_draft + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_DraftView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + put: + operationId: use_cases_put_draft + tags: [use_cases] + summary: use cases / put draft + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::put_draft + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + requestBody: + required: true + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_PutDraftRequest_request'} + x-marreq-bearer-scopes: ['use_cases:write'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + delete: + operationId: use_cases_discard_draft + tags: [use_cases] + summary: use cases / discard draft + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::discard_draft + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': {description: Deleted} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + x-marreq-bearer-scopes: ['use_cases:write'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/draft/operations: + post: + operationId: use_cases_draft_operations + tags: [use_cases] + summary: use cases / draft operations + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::draft_operations + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:write'] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + base_revision: + anyOf: + - &id003 {type: integer} + - {type: 'null'} + operations: + type: array + items: {$ref: '#/components/schemas/UseCaseOperation'} + additionalProperties: true + required: [operations] + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + /api/projects/{project_id}/use-cases/{id}/draft/publish: + post: + operationId: use_cases_publish + tags: [use_cases] + summary: use cases / publish + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::publish + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: *id002 + description: Safe replay for the same principal, target, operation and payload; changed payload returns + 409. + responses: + '201': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/UseCasePublishResponse'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:write'] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + base_revision: *id003 + change_note: + anyOf: + - *id002 + - {type: 'null'} + additionalProperties: true + required: [base_revision] + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + /api/projects/{project_id}/use-cases/{id}/versions: + get: + operationId: use_cases_list_versions + tags: [use_cases] + summary: use cases / list versions + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::list_versions + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_VersionSummary_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/versions/{version_id}: + get: + operationId: use_cases_get_version + tags: [use_cases] + summary: use cases / get version + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::get_version + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_VersionView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: + put: + operationId: use_cases_set_version_approval + tags: [use_cases] + summary: use cases / set version approval + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::set_version_approval + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesApprove] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_VersionView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + requestBody: + required: true + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_SetApprovalRequest_request'} + x-marreq-bearer-scopes: ['use_cases:approve'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/links: + get: + operationId: use_cases_list_links + tags: [use_cases] + summary: use cases / list links + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::list_links + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_LinkView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + post: + operationId: use_cases_create_link + tags: [use_cases] + summary: use cases / create link + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::create_link + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '201': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_LinkView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:write'] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + element_id: + anyOf: + - *id002 + - {type: 'null'} + requirement_id: + anyOf: + - *id003 + - {type: 'null'} + verification_id: + anyOf: + - *id003 + - {type: 'null'} + rationale: + anyOf: + - *id002 + - {type: 'null'} + additionalProperties: true + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + /api/projects/{project_id}/use-cases/{id}/links/{link_id}: + delete: + operationId: use_cases_delete_link + tags: [use_cases] + summary: use cases / delete link + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::delete_link + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': {description: Deleted} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + x-marreq-bearer-scopes: ['use_cases:write'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: + post: + operationId: use_cases_clear_link_suspect + tags: [use_cases] + summary: use cases / clear link suspect + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::clear_link_suspect + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_ClearSuspectResponse_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + x-marreq-bearer-scopes: ['use_cases:write'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/requirements/{requirement_id}/use-cases: + get: + operationId: use_cases_requirement_use_cases + tags: [use_cases] + summary: use cases / requirement use cases + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::requirement_use_cases + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_UseCaseRef_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/verifications/{verification_id}/use-cases: + get: + operationId: use_cases_verification_use_cases + tags: [use_cases] + summary: use cases / verification use cases + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::verification_use_cases + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_UseCaseRef_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/use-cases/{id}/draft/diff: + get: + operationId: use_cases_draft_diff + tags: [use_cases] + summary: use cases / draft diff + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::draft_diff + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: include_unchanged + required: false + schema: &id004 {type: boolean} responses: '200': - description: Public provider names only; no credentials or protocol secrets + description: Success content: application/json: - schema: - type: object - required: [password_enabled, external] - properties: - password_enabled: - type: boolean - external: - type: array - items: - type: object - required: [id, display_name] - properties: - id: { type: string } - display_name: { type: string } - /auth/external/{provider}/start: + schema: {$ref: '#/components/schemas/UseCaseDiff'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:read'] + /api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: get: - summary: Begin an external Authorization Code + PKCE login - operationId: authExternalStart + operationId: use_cases_version_diff + tags: [use_cases] + summary: use cases / version diff + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::version_diff + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] parameters: - - in: path - name: provider - required: true - schema: { type: string } - - in: query - name: return_to - schema: { type: string, example: /projects } + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: a + required: true + schema: {type: integer} + - in: path + name: b + required: true + schema: {type: integer} + - in: query + name: include_unchanged + required: false + schema: *id004 responses: - '303': { description: Redirect to the provider } - '400': { $ref: '#/components/responses/ApiError' } - '404': { $ref: '#/components/responses/ApiError' } - /auth/external/{provider}/callback: + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/UseCaseDiff'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:read'] + /api/projects/{project_id}/use-cases/{id}/comments: get: - summary: Consume a single-use external authentication callback - operationId: authExternalCallback + operationId: use_cases_list_comments + tags: [use_cases] + summary: use cases / list comments + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::list_comments + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] parameters: - - in: path - name: provider - required: true - schema: { type: string } - - in: query - name: code - schema: { type: string } - - in: query - name: state - schema: { type: string } - responses: - '303': { description: Login or link completed; redirect to the local return path } - '400': { $ref: '#/components/responses/ApiError' } - /auth/csrf: - get: - summary: Get or mint CSRF token - operationId: authCsrf + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: version_id + required: false + schema: + anyOf: + - {type: integer} + - {type: 'null'} + - in: query + name: element_id + required: false + schema: + anyOf: + - {type: string} + - {type: 'null'} responses: '200': - description: CSRF token for X-CSRF-Token header + description: Success content: application/json: schema: - type: object - required: [csrf_token] - properties: - csrf_token: - type: string - /auth/login: + type: array + items: {$ref: '#/components/schemas/use_cases_UseCaseCommentView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. post: - summary: Session login (JSON) - operationId: authLogin + operationId: use_cases_create_comment + tags: [use_cases] + summary: use cases / create comment + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::create_comment + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesWrite] parameters: - - in: header - name: X-CSRF-Token - required: true - schema: - type: string + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: *id002 + description: Safe replay for the same principal, target, operation and payload; changed payload returns + 409. + responses: + '201': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_UseCaseCommentView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:write'] requestBody: required: true content: application/json: schema: type: object - required: [username, password] properties: - username: - type: string - password: - type: string + body: *id002 + use_case_version_id: + anyOf: + - *id003 + - {type: 'null'} + element_id: + anyOf: + - *id002 + - {type: 'null'} + additionalProperties: true + required: [body] + x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow + trusted Origin/Referer fallback + /api/projects/{project_id}/baselines/{baseline_id}/use-cases: + get: + operationId: use_cases_baseline_use_cases + tags: [use_cases] + summary: use cases / baseline use cases + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::baseline_use_cases + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: {$ref: '#/components/schemas/use_cases_BaselineUseCaseView_response'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty + or framework generated; do not require an ApiError body.'} + '500': {$ref: '#/components/responses/ApiError'} + x-marreq-bearer-scopes: ['use_cases:read'] + description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict + semantics. + /api/projects/{project_id}/baselines/{baseline_id}/use-cases/{id}/diff/current: + get: + operationId: use_cases_baseline_use_case_diff + tags: [use_cases] + summary: use cases / baseline use case diff + x-marreq-variants: [server, cloud] + x-marreq-handler: use_cases::baseline_use_case_diff + x-marreq-source: marreq-core/src/api/use_cases.rs + x-marreq-guards: [ProjectUseCasesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: include_unchanged + required: false + schema: {type: boolean} responses: '200': - description: Logged in; Set-Cookie session + csrf + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/UseCaseDiff'} + '400': {$ref: '#/components/responses/ApiError'} + '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '404': {$ref: '#/components/responses/ApiError'} + '409': {$ref: '#/components/responses/ApiError'} + '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: ['use_cases:read'] + /api/users: + get: + operationId: users_list + tags: [users] + summary: users / list + x-marreq-variants: [server, cloud] + x-marreq-handler: users::list + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: users_create + tags: [users] + summary: users / create + x-marreq-variants: [server, cloud] + x-marreq-handler: users::create + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/users/{id}: + get: + operationId: users_get + tags: [users] + summary: users / get + x-marreq-variants: [server, cloud] + x-marreq-handler: users::get + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + put: + operationId: users_update + tags: [users] + summary: users / update + x-marreq-variants: [server, cloud] + x-marreq-handler: users::update + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: users_delete + tags: [users] + summary: users / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: users::delete + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/users/{id}/password: + put: + operationId: users_set_password + tags: [users] + summary: users / set password + x-marreq-variants: [server, cloud] + x-marreq-handler: users::set_password + x-marreq-source: marreq-core/src/api/users.rs + x-marreq-guards: [AdminOnly] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/verifications/{verification_id}/control: + get: + operationId: verification_control_get_verification_control + tags: [verification_control] + summary: verification control / get verification control + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_control::get_verification_control + x-marreq-source: marreq-core/src/api/verification_control.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + put: + operationId: verification_control_put_verification_control + tags: [verification_control] + summary: verification control / put verification control + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_control::put_verification_control + x-marreq-source: marreq-core/src/api/verification_control.rs + x-marreq-guards: [ProjectVerificationsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/{requirement_id}/close_out: + get: + operationId: verification_control_get_requirement_close_out + tags: [verification_control] + summary: verification control / get requirement close out + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_control::get_requirement_close_out + x-marreq-source: marreq-core/src/api/verification_control.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/close_out: + get: + operationId: verification_control_get_project_close_out + tags: [verification_control] + summary: verification control / get project close out + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_control::get_project_close_out + x-marreq-source: marreq-core/src/api/verification_control.rs + x-marreq-guards: [ProjectRequirementsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/requirements/{requirement_id}/compliance: + put: + operationId: verification_control_put_requirement_compliance + tags: [verification_control] + summary: verification control / put requirement compliance + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_control::put_requirement_compliance + x-marreq-source: marreq-core/src/api/verification_control.rs + x-marreq-guards: [ProjectRequirementsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verifications/{id}/snapshots: + get: + operationId: verification_diff_list_snapshots_by_project + tags: [verification_diff] + summary: verification diff / list snapshots by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_diff::list_snapshots_by_project + x-marreq-source: marreq-core/src/api/verification_diff.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: + get: + operationId: verification_diff_diff_snapshots_by_project + tags: [verification_diff] + summary: verification diff / diff snapshots by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_diff::diff_snapshots_by_project + x-marreq-source: marreq-core/src/api/verification_diff.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: v1 + required: true + schema: {type: integer} + - in: path + name: v2 + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: + get: + operationId: verification_diff_diff_baseline_vs_current + tags: [verification_diff] + summary: verification diff / diff baseline vs current + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_diff::diff_baseline_vs_current + x-marreq-source: marreq-core/src/api/verification_diff.rs + x-marreq-guards: [ProjectVerificationsAndBaselinesRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verification-methods: + get: + operationId: verification_methods_list_by_project + tags: [verification_methods] + summary: verification methods / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_methods::list_by_project + x-marreq-source: marreq-core/src/api/verification_methods.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: verification_methods_create_by_project + tags: [verification_methods] + summary: verification methods / create by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_methods::create_by_project + x-marreq-source: marreq-core/src/api/verification_methods.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verification-methods/{method_id}: + put: + operationId: verification_methods_update_by_project + tags: [verification_methods] + summary: verification methods / update by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_methods::update_by_project + x-marreq-source: marreq-core/src/api/verification_methods.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: method_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + delete: + operationId: verification_methods_delete_by_project + tags: [verification_methods] + summary: verification methods / delete by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_methods::delete_by_project + x-marreq-source: marreq-core/src/api/verification_methods.rs + x-marreq-guards: [ProjectAccessOrBearer] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: method_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/verification-status: + get: + operationId: verification_status_list_verification_statuses + tags: [verification_status] + summary: verification status / list verification statuses + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_status::list_verification_statuses + x-marreq-source: marreq-core/src/api/verification_status.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: verification_status_create_verification_status + tags: [verification_status] + summary: verification status / create verification status + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_status::create_verification_status + x-marreq-source: marreq-core/src/api/verification_status.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/verification-status/{id}: + get: + operationId: verification_status_get_verification_status + tags: [verification_status] + summary: verification status / get verification status + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_status::get_verification_status + x-marreq-source: marreq-core/src/api/verification_status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + put: + operationId: verification_status_update_verification_status + tags: [verification_status] + summary: verification status / update verification status + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_status::update_verification_status + x-marreq-source: marreq-core/src/api/verification_status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: verification_status_delete_verification_status + tags: [verification_status] + summary: verification status / delete verification status + x-marreq-variants: [server, cloud] + x-marreq-handler: verification_status::delete_verification_status + x-marreq-source: marreq-core/src/api/verification_status.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/verifications: + get: + operationId: verifications_list + tags: [verifications] + summary: verifications / list + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::list + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + post: + operationId: verifications_create + tags: [verifications] + summary: verifications / create + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::create + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ApiUser] + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/verifications: + get: + operationId: verifications_list_by_project + tags: [verifications] + summary: verifications / list by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::list_by_project + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + post: + operationId: verifications_create_by_project + tags: [verifications] + summary: verifications / create by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::create_by_project + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ProjectVerificationsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/projects/{project_id}/verifications/{id}: + get: + operationId: verifications_get_by_project + tags: [verifications] + summary: verifications / get by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::get_by_project + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ProjectVerificationsRead] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + put: + operationId: verifications_update_by_project + tags: [verifications] + summary: verifications / update by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::update_by_project + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ProjectVerificationsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/verifications/{id}: + get: + operationId: verifications_get + tags: [verifications] + summary: verifications / get + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::get + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + delete: + operationId: verifications_delete + tags: [verifications] + summary: verifications / delete + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::delete + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/verifications/{id}/field: + post: + operationId: verifications_update_field + tags: [verifications] + summary: verifications / update field + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::update_field + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ApiUser] + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/verifications/{id}/field: + post: + operationId: verifications_update_field_by_project + tags: [verifications] + summary: verifications / update field by project + x-marreq-variants: [server, cloud] + x-marreq-handler: verifications::update_field_by_project + x-marreq-source: marreq-core/src/api/verifications.rs + x-marreq-guards: [ProjectVerificationsWrite] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/auth/register: + post: + operationId: auth_public_auth_register + tags: [auth_public] + summary: auth public / auth register + x-marreq-variants: [cloud] + x-marreq-handler: auth_public::auth_register + x-marreq-source: marreq-cloud/src/api/auth_public.rs + x-marreq-guards: [] + parameters: [] + responses: + '201': + description: Success content: application/json: schema: type: object properties: - status: - type: string - example: ok - user: - type: object - '400': - $ref: '#/components/responses/ApiError' - /auth/logout: - post: - summary: End session - operationId: authLogout - parameters: - - in: header - name: X-CSRF-Token - required: true - schema: - type: string - responses: + status: {type: string, const: ok} + required: [status] + additionalProperties: true '200': - description: Logged out + description: Success content: application/json: schema: type: object properties: - status: - type: string - example: ok - /auth/me: + status: {type: string, const: ok} + required: [status] + additionalProperties: true + '400': {$ref: '#/components/responses/ApiError'} + '500': {$ref: '#/components/responses/ApiError'} + description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing + email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: *id001 + name: *id001 + email: *id001 + password: *id001 + required: [username, name, email, password] + additionalProperties: true + /api/auth/verify-email: get: - summary: Current user (session) - operationId: authMe - security: - - cookieAuth: [] + operationId: auth_public_auth_verify_email + tags: [auth_public] + summary: auth public / auth verify email + x-marreq-variants: [cloud] + x-marreq-handler: auth_public::auth_verify_email + x-marreq-source: marreq-cloud/src/api/auth_public.rs + x-marreq-guards: [] + parameters: + - in: query + name: token + required: true + schema: *id001 responses: '200': - description: Current user + description: Success content: application/json: schema: type: object - '401': - $ref: '#/components/responses/ApiError' - /dashboard: - get: - summary: SPA dashboard payload (user + decorated projects + CSRF) - operationId: dashboardJson - security: - - cookieAuth: [] + properties: + status: {type: string, const: ok} + required: [status] + additionalProperties: true + '400': {$ref: '#/components/responses/ApiError'} + '500': {$ref: '#/components/responses/ApiError'} + description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing + email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + /api/auth/forgot-password: + post: + operationId: auth_public_auth_forgot_password + tags: [auth_public] + summary: auth public / auth forgot password + x-marreq-variants: [cloud] + x-marreq-handler: auth_public::auth_forgot_password + x-marreq-source: marreq-cloud/src/api/auth_public.rs + x-marreq-guards: [] + parameters: [] responses: '200': - description: Dashboard context for the home view + description: Success content: application/json: schema: type: object - '401': - $ref: '#/components/responses/ApiError' - /projects: - get: - summary: List projects for session user - operationId: listProjectsSession - security: - - cookieAuth: [] + properties: + status: {type: string, const: ok} + required: [status] + additionalProperties: true + '400': {$ref: '#/components/responses/ApiError'} + '500': {$ref: '#/components/responses/ApiError'} + description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing + email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + email: *id001 + required: [email] + additionalProperties: true + /api/auth/reset-password: + post: + operationId: auth_public_auth_reset_password + tags: [auth_public] + summary: auth public / auth reset password + x-marreq-variants: [cloud] + x-marreq-handler: auth_public::auth_reset_password + x-marreq-source: marreq-cloud/src/api/auth_public.rs + x-marreq-guards: [] + parameters: [] responses: '200': - description: Project list + description: Success content: application/json: schema: - type: array - items: - type: object - '401': - $ref: '#/components/responses/ApiError' - + type: object + properties: + status: {type: string, const: ok} + required: [status] + additionalProperties: true + '400': {$ref: '#/components/responses/ApiError'} + '500': {$ref: '#/components/responses/ApiError'} + description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing + email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + token: *id001 + new_password: *id001 + required: [token, new_password] + additionalProperties: true components: securitySchemes: - cookieAuth: - type: apiKey - in: cookie - name: session - description: | - Rocket private session cookie: typically `session` on HTTP (localhost) or - `__Host-session` when `MARREQ_SECURE_SESSION_COOKIE=1` over HTTPS. - Use `fetch(..., { credentials: 'include' })`. - responses: + cookieAuth: {type: apiKey, in: cookie, name: session, description: Rocket private session; __Host-session on + secure deployments. Mutations require X-CSRF-Token.} + bearerAuth: {type: http, scheme: bearer, description: 'Personal API token or delegated OAuth access token; project + membership, domain scopes and project permissions apply.'} + mcpServiceAuth: {type: apiKey, in: header, name: X-Marreq-Mcp-Audit-Secret, description: Private service-to-service + audit secret; never browser or delegated caller configuration.} + schemas: + external_ProviderDiscovery_response: + type: object + properties: + password_enabled: {type: boolean} + external: + type: array + items: {$ref: '#/components/schemas/external_PublicAuthProvider_response'} + additionalProperties: true + required: [password_enabled, external] + x-rust-model: marreq-core/src/auth/external.rs::ProviderDiscovery + external_PublicAuthProvider_response: + type: object + properties: + id: {type: string} + display_name: {type: string} + additionalProperties: true + required: [id, display_name] + x-rust-model: marreq-core/src/auth/external.rs::PublicAuthProvider + entities_User_response: + type: object + properties: + id: {type: integer} + username: {type: string} + name: {type: string} + email: {type: string} + creation_date: {type: string} + last_login: {type: string} + is_admin: {type: boolean} + email_verified: {type: boolean} + additionalProperties: true + required: [id, username, name, email, creation_date, last_login, is_admin, email_verified] + x-rust-model: marreq-core/src/models/entities.rs::User + use_cases_UseCaseRow_response: + type: object + properties: + id: {type: integer} + reference_code: {type: string} + title: {type: string} + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + step_count: {type: integer} + alternative_count: {type: integer} + exception_count: {type: integer} + approval_state: + anyOf: + - {type: string} + - {type: 'null'} + current_version_number: + anyOf: + - {type: integer} + - {type: 'null'} + has_draft: {type: boolean} + link_count: {type: integer} + suspect_count: {type: integer} + updated_at: {type: string} + additionalProperties: true + required: [id, reference_code, title, primary_actor, step_count, alternative_count, exception_count, approval_state, + current_version_number, has_draft, link_count, suspect_count, updated_at] + x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseRow + use_cases_NextCode_response: + type: object + properties: + reference_code: {type: string} + additionalProperties: true + required: [reference_code] + x-rust-model: marreq-core/src/api/use_cases.rs::NextCode + use_cases_UseCaseDetail_response: + type: object + properties: + id: {type: integer} + project_id: {type: integer} + reference_code: {type: string} + title: {type: string} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_at: {type: string} + updated_at: {type: string} + current_version: + anyOf: + - {$ref: '#/components/schemas/use_cases_VersionView_response'} + - {type: 'null'} + draft: + anyOf: + - {$ref: '#/components/schemas/use_cases_DraftSummary_response'} + - {type: 'null'} + additionalProperties: true + required: [id, project_id, reference_code, title, created_by, created_at, updated_at, current_version, draft] + x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseDetail + use_cases_VersionView_response: + type: object + properties: + id: {type: integer} + version_number: {type: integer} + approval_state: {type: string} + title: {type: string} + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + change_note: + anyOf: + - {type: string} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_by_name: + anyOf: + - {type: string} + - {type: 'null'} + created_at: {type: string} + reviewed_by: + anyOf: + - {type: integer} + - {type: 'null'} + reviewed_at: + anyOf: + - {type: string} + - {type: 'null'} + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} + labels: + type: object + additionalProperties: {type: string} + additionalProperties: true + required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, + created_at, reviewed_by, reviewed_at, approved_by, approved_at, document, labels] + x-rust-model: marreq-core/src/api/use_cases.rs::VersionView + use_cases_VersionSummary_response: + type: object + properties: + id: {type: integer} + version_number: {type: integer} + approval_state: {type: string} + title: {type: string} + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + change_note: + anyOf: + - {type: string} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_by_name: + anyOf: + - {type: string} + - {type: 'null'} + created_at: {type: string} + reviewed_by: + anyOf: + - {type: integer} + - {type: 'null'} + reviewed_at: + anyOf: + - {type: string} + - {type: 'null'} + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + additionalProperties: true + required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, + created_at, reviewed_by, reviewed_at, approved_by, approved_at] + x-rust-model: marreq-core/src/api/use_cases.rs::VersionSummary + use_cases_DraftSummary_response: + type: object + properties: + revision: {type: integer} + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: {type: string} + additionalProperties: true + required: [revision, base_version_id, updated_by, updated_by_name, updated_at] + x-rust-model: marreq-core/src/api/use_cases.rs::DraftSummary + use_cases_DraftView_response: + type: object + properties: + use_case_id: {type: integer} + revision: {type: integer} + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: {type: string} + document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} + labels: + type: object + additionalProperties: {type: string} + additionalProperties: true + required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels] + x-rust-model: marreq-core/src/api/use_cases.rs::DraftView + use_cases_PutDraftRequest_request: + type: object + properties: + base_revision: + anyOf: + - {type: integer} + - {type: 'null'} + document: {$ref: '#/components/schemas/UseCaseDocument'} + additionalProperties: true + required: [document] + x-rust-model: marreq-core/src/api/use_cases.rs::PutDraftRequest + use_cases_DraftChangeView_response: + type: object + properties: + use_case_id: {type: integer} + revision: {type: integer} + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: {type: string} + document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} + labels: + type: object + additionalProperties: {type: string} + effects: + type: array + items: {$ref: '#/components/schemas/UseCaseEffect'} + additionalProperties: true + required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels, + effects] + x-rust-model: marreq-core/src/api/use_cases.rs::DraftChangeView + use_cases_SetApprovalRequest_request: + type: object + properties: + state: {type: string} + additionalProperties: true + required: [state] + x-rust-model: marreq-core/src/api/use_cases.rs::SetApprovalRequest + use_cases_LinkView_response: + type: object + properties: + id: {type: integer} + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + target_type: + type: string + enum: [requirement, verification] + requirement_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_id: + anyOf: + - {type: integer} + - {type: 'null'} + target_reference_code: {type: string} + target_title: {type: string} + rationale: + anyOf: + - {type: string} + - {type: 'null'} + suspect: {type: boolean} + suspect_at: + anyOf: + - {type: string} + - {type: 'null'} + suspect_reason: + anyOf: + - {type: string} + - {type: 'null'} + linked_requirement_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + current_requirement_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_at: {type: string} + additionalProperties: true + required: [id, element_id, element_label, target_type, requirement_id, verification_id, target_reference_code, + target_title, rationale, suspect, suspect_at, suspect_reason, linked_requirement_version_id, current_requirement_version_id, + created_by, created_at] + x-rust-model: marreq-core/src/api/use_cases.rs::LinkView + use_cases_ClearSuspectResponse_response: + type: object + properties: + status: {type: string} + cleared: {type: boolean} + additionalProperties: true + required: [status, cleared] + x-rust-model: marreq-core/src/api/use_cases.rs::ClearSuspectResponse + use_cases_UseCaseRef_response: + type: object + properties: + use_case_id: {type: integer} + reference_code: {type: string} + title: {type: string} + link_id: {type: integer} + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + suspect: {type: boolean} + additionalProperties: true + required: [use_case_id, reference_code, title, link_id, element_id, element_label, suspect] + x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseRef + use_cases_UseCaseCommentView_response: + type: object + properties: + id: {type: integer} + use_case_id: {type: integer} + use_case_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + author_id: {type: integer} + author_name: {type: string} + body: {type: string} + created_at: {type: string} + additionalProperties: true + required: [id, use_case_id, use_case_version_id, element_id, element_label, author_id, author_name, body, + created_at] + x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseCommentView + use_cases_BaselineUseCaseView_response: + type: object + properties: + use_case_id: {type: integer} + reference_code: {type: string} + title: {type: string} + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + version_id: {type: integer} + version_number: {type: integer} + approval_state: {type: string} + current_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + current_version_number: + anyOf: + - {type: integer} + - {type: 'null'} + link_count: {type: integer} + additionalProperties: true + required: [use_case_id, reference_code, title, primary_actor, version_id, version_number, approval_state, + current_version_id, current_version_number, link_count] + x-rust-model: marreq-core/src/api/use_cases.rs::BaselineUseCaseView ApiError: - description: Structured error - content: - application/json: - schema: + type: object + required: [status, error, message] + properties: + status: {type: integer} + error: {type: string} + message: {type: string} + additionalProperties: true + BuildInfo: + type: object + properties: + backend_version: *id005 + backend_git_sha: *id005 + deployment_mode: + type: string + enum: [server, cloud] + frontend_compatibility: + type: object + properties: + min_version: *id005 + max_version: *id005 + required: [min_version, max_version] + additionalProperties: true + required: [backend_version, backend_git_sha, deployment_mode, frontend_compatibility] + additionalProperties: true + UseCaseElement: + type: object + properties: + id: &id006 {type: string} + text: *id006 + additionalProperties: false + required: [id, text] + UseCaseBranchEnd: + oneOf: + - type: object + properties: + type: {const: rejoin, type: string} + step: *id006 + additionalProperties: false + required: [type, step] + - type: object + properties: + type: {const: end, type: string} + outcome: + type: string + enum: [success, failure] + additionalProperties: false + required: [type, outcome] + UseCaseDocument: + type: object + properties: + schema: {type: string, const: marreq.use-case.v1} + title: *id006 + primary_actor: + anyOf: + - *id006 + - {type: 'null'} + secondary_actors: + type: array + items: *id006 + summary: + anyOf: + - *id006 + - {type: 'null'} + preconditions: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + main: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + branches: + type: array + items: {$ref: '#/components/schemas/UseCaseBranch'} + postconditions: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + additionalProperties: false + required: [title] + UseCaseBranch: + type: object + properties: + id: *id006 + kind: + type: string + enum: [alternative, exception] + title: *id006 + from_step: *id006 + end: {$ref: '#/components/schemas/UseCaseBranchEnd'} + steps: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + additionalProperties: false + required: [id, kind, title, from_step, end] + UseCaseEffect: + oneOf: + - type: object + properties: + type: {type: string, const: relabelled} + id: *id006 + from: *id006 + to: *id006 + additionalProperties: true + required: [type, id, from, to] + - type: object + properties: + type: {type: string, const: removed} + id: *id006 + additionalProperties: true + required: [type, id] + - type: object + properties: + type: {type: string, const: rebranched} + id: *id006 + from_step: *id006 + additionalProperties: true + required: [type, id, from_step] + - type: object + properties: + type: {type: string, const: created} + id: *id006 + additionalProperties: true + required: [type, id] + DeploymentInfo: + type: object + properties: + mode: + type: string + enum: [server, cloud] + allows_self_registration: &id007 {type: boolean} + requires_email_verification: *id007 + allows_admin_promotion: *id007 + assigns_personal_workspace: *id007 + allows_self_administered_user_creation: *id007 + allows_database_backup: *id007 + required: [mode, allows_self_registration, requires_email_verification, allows_admin_promotion, assigns_personal_workspace, + allows_self_administered_user_creation, allows_database_backup] + additionalProperties: true + McpPrincipalResponse: + type: object + properties: + user_id: {type: integer} + authentication_type: {type: string} + principal_id: {type: string} + client_id: + anyOf: + - {type: string} + - {type: 'null'} + grant_id: + anyOf: + - {type: integer} + - {type: 'null'} + additionalProperties: true + required: [user_id, authentication_type, principal_id, client_id, grant_id] + x-rust-model: marreq-core/src/api/mcp.rs::McpPrincipalResponse + UseCaseDocumentResponse: + type: object + properties: + schema: {type: string, const: marreq.use-case.v1} + title: &id008 {type: string} + primary_actor: + anyOf: + - *id008 + - {type: 'null'} + secondary_actors: + type: array + items: *id008 + summary: + anyOf: + - *id008 + - {type: 'null'} + preconditions: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + main: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + branches: + type: array + items: {$ref: '#/components/schemas/UseCaseBranchResponse'} + postconditions: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + additionalProperties: false + required: [schema, title, primary_actor, secondary_actors, summary, preconditions, main, branches, postconditions] + UseCaseBranchResponse: + type: object + properties: + id: &id009 {type: string} + kind: + type: string + enum: [alternative, exception] + title: *id009 + from_step: *id009 + end: {$ref: '#/components/schemas/UseCaseBranchEnd'} + steps: + type: array + items: {$ref: '#/components/schemas/UseCaseElement'} + additionalProperties: false + required: [id, kind, title, from_step, end, steps] + UseCaseListRef: + oneOf: + - type: string + enum: [preconditions, main, postconditions] + - type: object + properties: + branch: *id002 + additionalProperties: true + required: [branch] + UseCaseNewElement: + type: object + properties: + id: + anyOf: + - *id002 + - {type: 'null'} + text: *id002 + additionalProperties: false + required: [text] + UseCaseNewBranch: + type: object + properties: + id: + anyOf: + - *id002 + - {type: 'null'} + kind: + type: string + enum: [alternative, exception] + title: *id002 + from_step: *id002 + end: {$ref: '#/components/schemas/UseCaseBranchEnd'} + steps: + type: array + items: {$ref: '#/components/schemas/UseCaseNewElement'} + additionalProperties: false + required: [kind, title, from_step, end] + UseCaseDependents: + oneOf: + - type: string + enum: [cascade] + - type: object + properties: + rebranch_to: *id002 + additionalProperties: true + required: [rebranch_to] + UseCaseOperation: + oneOf: + - type: object + properties: + op: {type: string, const: set_header} + title: + anyOf: + - *id002 + - {type: 'null'} + primary_actor: + anyOf: + - *id002 + - {type: 'null'} + secondary_actors: + anyOf: + - type: array + items: *id002 + - {type: 'null'} + summary: + anyOf: + - *id002 + - {type: 'null'} + additionalProperties: false + required: [op] + - type: object + properties: + op: {type: string, const: add_element} + list: {$ref: '#/components/schemas/UseCaseListRef'} + after: + anyOf: + - *id002 + - {type: 'null'} + element: {$ref: '#/components/schemas/UseCaseNewElement'} + additionalProperties: false + required: [op, list, element] + - type: object + properties: + op: {type: string, const: update_text} + id: *id002 + text: *id002 + additionalProperties: false + required: [op, id, text] + - type: object + properties: + op: {type: string, const: move_element} + id: *id002 + list: + anyOf: + - {$ref: '#/components/schemas/UseCaseListRef'} + - {type: 'null'} + after: + anyOf: + - *id002 + - {type: 'null'} + additionalProperties: false + required: [op, id] + - type: object + properties: + op: {type: string, const: remove_element} + id: *id002 + dependents: + anyOf: + - {$ref: '#/components/schemas/UseCaseDependents'} + - {type: 'null'} + additionalProperties: false + required: [op, id] + - type: object + properties: + op: {type: string, const: add_branch} + branch: {$ref: '#/components/schemas/UseCaseNewBranch'} + additionalProperties: false + required: [op, branch] + - type: object + properties: + op: {type: string, const: update_branch} + id: *id002 + title: + anyOf: + - *id002 + - {type: 'null'} + kind: + anyOf: + - type: string + enum: [alternative, exception] + - {type: 'null'} + from_step: + anyOf: + - *id002 + - {type: 'null'} + end: + anyOf: + - {$ref: '#/components/schemas/UseCaseBranchEnd'} + - {type: 'null'} + additionalProperties: false + required: [op, id] + - type: object + properties: + op: {type: string, const: remove_branch} + id: *id002 + additionalProperties: false + required: [op, id] + UseCaseDiffSide: + type: object + properties: + list: + type: string + enum: [preconditions, main, postconditions, branch, branches] + branch_id: *id002 + label: + anyOf: + - *id002 + - {type: 'null'} + text: *id002 + from_step: *id002 + end: {$ref: '#/components/schemas/UseCaseBranchEnd'} + kind: + type: string + enum: [alternative, exception] + additionalProperties: true + required: [list, label, text] + UseCaseElementDiff: + type: object + properties: + id: *id002 + kind: + type: string + enum: [precondition, postcondition, step, branch] + changes: + type: array + items: + type: string + enum: [added, removed, edited, moved, rebranched] + old: + anyOf: + - {$ref: '#/components/schemas/UseCaseDiffSide'} + - {type: 'null'} + new: + anyOf: + - {$ref: '#/components/schemas/UseCaseDiffSide'} + - {type: 'null'} + text_diff: + type: array + items: type: object properties: - status: - type: integer - error: - type: string - message: + op: type: string + enum: [equal, delete, insert] + text: *id002 + additionalProperties: true + required: [op, text] + additionalProperties: true + required: [id, kind, changes, old, new] + UseCaseDiffEnd: + type: object + properties: + version_id: + anyOf: + - *id003 + - {type: 'null'} + version_number: + anyOf: + - *id003 + - {type: 'null'} + draft_revision: + anyOf: + - *id003 + - {type: 'null'} + additionalProperties: true + required: [version_id, version_number, draft_revision] + UseCaseDiff: + type: object + properties: + from: + anyOf: + - {$ref: '#/components/schemas/UseCaseDiffEnd'} + - {type: 'null'} + to: {$ref: '#/components/schemas/UseCaseDiffEnd'} + summary: + type: object + properties: + added: *id003 + removed: *id003 + edited: *id003 + moved: *id003 + rebranched: *id003 + additionalProperties: true + required: [added, removed, edited, moved, rebranched] + header: + type: object + additionalProperties: + type: object + properties: + old: &id010 + oneOf: + - *id002 + - {type: 'null'} + - type: array + items: *id002 + new: *id010 + additionalProperties: true + required: [old, new] + elements: + type: array + items: {$ref: '#/components/schemas/UseCaseElementDiff'} + additionalProperties: true + required: [from, to, summary, header, elements] + UseCasePublishResponse: + type: object + properties: + version: {$ref: '#/components/schemas/use_cases_VersionView_response'} + removed_links: + type: array + items: {$ref: '#/components/schemas/use_cases_LinkView_response'} + additionalProperties: true + required: [version, removed_links] + responses: + ApiError: + description: API error; use cases may include code and structured details. Guard/fairing errors can have an + empty body. + content: + application/json: + schema: {$ref: '#/components/schemas/ApiError'} +x-marreq-contract-complete: false +security: +- cookieAuth: [] diff --git a/docs/developer/use-cases-api.md b/docs/developer/use-cases-api.md index 227257de..6340723c 100644 --- a/docs/developer/use-cases-api.md +++ b/docs/developer/use-cases-api.md @@ -166,3 +166,14 @@ Comments are immutable. `@username` mentions of project members send a `mentione - **Search.** `GET /api/search` hits have `kind: "requirement" | "use_case"` and `use_case_id`; use cases match on code, title, actors and step text. - **Project bundle.** `bundle.json` has an optional `use_cases` section (see the HTTP API contract). - **MCP.** `list_use_cases`, `get_use_case`, `get_use_case_diff` and `list_use_case_links` (`use_cases:read`); `create_use_case`, `edit_use_case_draft`, `publish_use_case_draft` and `link_use_case_element` (`use_cases:write`, `MARREQ_MODE=draft_write`). Approval is not exposed to agents. See [mcp-setup.md](mcp-setup.md). + +## Machine-readable contract + +The use-case routes and wire schemas are described in `openapi.yaml`, including +request defaults, response nullability, draft-operation tagged unions, version +publishing, links, comments and diffs. Derived TypeScript models are generated by +`scripts/contracts/generate.py`; do not hand-edit `generated-contract.ts`. +The isolated backend contract smoke test covers representative list/create/draft +operations, delegated scope rejection and project boundaries. Full API contract +coverage and broader cross-version evidence remain tracked in the P0 audit in +[http-api-contract.md](http-api-contract.md#p0-contract-hardening-audit-and-current-coverage-423). diff --git a/docs/releases.md b/docs/releases.md index 9f194078..645e74e7 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -81,3 +81,131 @@ Images carry OCI label `org.opencontainers.image.version`. - Frontend: Vite `define` (`__FRONTEND_VERSION__`, `__FRONTEND_GIT_SHA__`) in `frontend/vite.config.ts`. When `MARREQ_VERSION` is unset or empty, each artifact falls back to its manifest version. A leading `v` is stripped so the value stays plain `X.Y.Z` for the compatibility check. Local images: `make docker-server MARREQ_VERSION=0.1.3`. + +## API compatibility gate and retained artifact contracts (#423 P0) + +The P0 bootstrap is **not release-ready**: 206 HTTP operations still lack audited +wire contracts. `API contracts` CI validates the document, generated artifacts, +source-review evidence, both compiled route inventories, negative historical +fixtures and representative real producer/consumer exchanges. Release publication +also requires `scripts/contracts/check.py --require-complete`, which currently +fails intentionally. This must remain a draft until the coverage gaps described in +[the HTTP contract audit](developer/http-api-contract.md#p0-contract-hardening-audit-and-current-coverage-423) +are closed. + +### Compatibility policy + +Component SemVer describes each artifact independently. Equal versions are never +required. A consumer declares `requires_backend_min` and optionally an inclusive +`requires_backend_max`; frontend and MCP metadata are validated independently. +The backend's existing frontend acceptance range is also checked. Stable +`X.Y.Z` versions are supported; prerelease and wildcard ranges fail closed. +Omitting an upper bound is appropriate only with executable retained-contract +checks; an upper bound is a temporary tested-support ceiling, not proof that +all versions inside it work. Existing runtime `CompatibilityBanner` warnings are +preserved for manually assembled deployments. + +A producer must continue accepting every previously valid consumer request and +returning responses accepted by that released consumer. Additive optional request +fields and additive response fields are compatible when the consumer accepts +unknown properties. Removing an endpoint or guaranteed response property, +changing a field type/nullability, adding a required request field, restricting a +request enum, extending a response enum used by exhaustive consumers, changing +status/media/header behavior or changing authentication/scopes is potentially +breaking. Breaking changes require a new API-compatible deployment combination +and a component SemVer breaking release; while 0.x, treat minor changes with the +same explicit care rather than assuming all 0.x releases interoperate. Unknown +changed schema constraints/unions are rejected conservatively for manual review. + +Previously released consumers retain their own contracts and policy. A newer +backend must satisfy those old contracts, not regenerated contracts from current +`main`. During rolling upgrades, check each backend/consumer combination that +can coexist, including old and new replicas; upgrade consumers first only when +the old backend also passes. No gate establishes database migration safety; +that remains a separate P0 workstream in #423. + +### Export and retain an exact component contract + +After committing audited contract inputs on the component's source commit: + +```sh +python3 scripts/contracts/bundle.py --component backend --variant server \ + --version 0.2.4 --output backend-0.2.4.json +python3 scripts/contracts/bundle.py --component frontend \ + --version 0.2.1 --output frontend-0.2.1.json +python3 scripts/contracts/bundle.py --component mcp \ + --version 0.3.0 --output mcp-0.3.0.json +``` + +Run each command on **that artifact's own source commit**, not all on current +`main`. The exporter refuses dirty audited inputs and records the exact source +SHA. Retain the JSON next to the versioned image/release with its SHA-256. Images +also carry their build-time OpenAPI and compatibility metadata. The exporter can +record incomplete bootstrap evidence for review, but the gate cannot certify it. +Old releases without retained evidence require a real audit on their source +commit; missing evidence never falls back to current sources. + +### Verify the resulting deployment, including unchanged artifacts + +Prepare a `deployment.json` with exactly these top-level fields: + +```json +{ + "format": "marreq.deployment-contract.v1", + "variant": "server", + "backend": { + "file": "backend-0.2.4.json", + "sha256": "<64 lowercase hex characters>", + "version": "0.2.4", + "source_sha": "<40 lowercase hex characters>" + }, + "frontend": { + "file": "frontend-0.2.1.json", + "sha256": "<64 lowercase hex characters>", + "version": "0.2.1", + "source_sha": "<40 lowercase hex characters>" + }, + "mcp": { + "file": "mcp-0.3.0.json", + "sha256": "<64 lowercase hex characters>", + "version": "0.3.0", + "source_sha": "<40 lowercase hex characters>" + } +} +``` + +Place referenced bundles inside the plan directory. Verify offline: + +```sh +scripts/check-api-compatibility.sh --manifest deployment.json +``` + +Exit 0 means all checked declarations/contracts are compatible. Exit 1 means +incompatibility, incomplete evidence, invalid metadata, wrong deployment variant, +a missing artifact, or a digest/source/version mismatch. Output is structured JSON +with component-specific diagnostics. The comparator checks request acceptance in +the consumer-to-producer direction and response acceptance in the reverse +direction. This command does not download artifacts, execute plans, send tokens, +publish images or deploy anything. SHA-256 pins content; trusted provenance for +retrieving artifacts remains the release/deployment orchestrator's responsibility. + +### Current tag workflow and future global releases + +`release-artifacts.yml` retains the existing component tag triggers. Every image +publisher depends on contract tests and the mandatory deployment gate. Once P0 +coverage is complete, prepare a separate immutable revision in this repository +containing `deployment.json` and all referenced bundles; set the repository variable +`MARREQ_RELEASE_CONTRACTS_SHA` to that full commit SHA. This revision is prepared +**after** the candidate source commit exists, avoiding self-referential source +hashes in a manifest committed with its own candidate. The workflow checks out +that exact plan revision, exports the candidate contract from the tagged source, +and checks its digest, version and source SHA against the plan. Unchanged +frontend/MCP/backend bundles are never replaced with the tag checkout's versions. +The candidate contract is retained as a workflow artifact before image publishing. +No default production version or fabricated historical manifest is provided. + +The future global-release/selective-deployment pipeline should retrieve trusted +retained bundles, construct every resulting deployment plan, and invoke this +same command plus the reusable `api-contracts.yml` workflow. Global version +registry/tag migration, image digest provenance, actual CD, database migrations, +rollback and VPS configuration remain in #423. Nothing here deploys to the VPS. diff --git a/frontend/src/api/generated-contract.ts b/frontend/src/api/generated-contract.ts new file mode 100644 index 00000000..5e16cda6 --- /dev/null +++ b/frontend/src/api/generated-contract.ts @@ -0,0 +1,450 @@ +// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml. +// Do not edit; this file carries wire types, not a second contract. + +export type ApiError = { + "status": number; + "error": string; + "message": string; + [key: string]: unknown; +}; + +export type BuildInfo = { + "backend_version": string; + "backend_git_sha": string; + "deployment_mode": "server" | "cloud"; + "frontend_compatibility": { + "min_version": string; + "max_version": string; + [key: string]: unknown; +}; + [key: string]: unknown; +}; + +export type DeploymentInfo = { + "mode": "server" | "cloud"; + "allows_self_registration": boolean; + "requires_email_verification": boolean; + "allows_admin_promotion": boolean; + "assigns_personal_workspace": boolean; + "allows_self_administered_user_creation": boolean; + "allows_database_backup": boolean; + [key: string]: unknown; +}; + +export type McpPrincipalResponse = { + "user_id": number; + "authentication_type": string; + "principal_id": string; + "client_id": (string | null); + "grant_id": (number | null); + [key: string]: unknown; +}; + +export type UseCaseBranch = { + "id": string; + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps"?: Array; +}; + +export type UseCaseBranchEnd = ({ + "type": "rejoin"; + "step": string; +} | { + "type": "end"; + "outcome": "success" | "failure"; +}); + +export type UseCaseBranchResponse = { + "id": string; + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps": Array; +}; + +export type UseCaseDependents = ("cascade" | { + "rebranch_to": string; + [key: string]: unknown; +}); + +export type UseCaseDiff = { + "from": (UseCaseDiffEnd | null); + "to": UseCaseDiffEnd; + "summary": { + "added": number; + "removed": number; + "edited": number; + "moved": number; + "rebranched": number; + [key: string]: unknown; +}; + "header": { + [key: string]: { + "old": (string | null | Array); + "new": (string | null | Array); + [key: string]: unknown; +}; +}; + "elements": Array; + [key: string]: unknown; +}; + +export type UseCaseDiffEnd = { + "version_id": (number | null); + "version_number": (number | null); + "draft_revision": (number | null); + [key: string]: unknown; +}; + +export type UseCaseDiffSide = { + "list": "preconditions" | "main" | "postconditions" | "branch" | "branches"; + "branch_id"?: string; + "label": (string | null); + "text": string; + "from_step"?: string; + "end"?: UseCaseBranchEnd; + "kind"?: "alternative" | "exception"; + [key: string]: unknown; +}; + +export type UseCaseDocument = { + "schema"?: "marreq.use-case.v1"; + "title": string; + "primary_actor"?: (string | null); + "secondary_actors"?: Array; + "summary"?: (string | null); + "preconditions"?: Array; + "main"?: Array; + "branches"?: Array; + "postconditions"?: Array; +}; + +export type UseCaseDocumentResponse = { + "schema": "marreq.use-case.v1"; + "title": string; + "primary_actor": (string | null); + "secondary_actors": Array; + "summary": (string | null); + "preconditions": Array; + "main": Array; + "branches": Array; + "postconditions": Array; +}; + +export type UseCaseEffect = ({ + "type": "relabelled"; + "id": string; + "from": string; + "to": string; + [key: string]: unknown; +} | { + "type": "removed"; + "id": string; + [key: string]: unknown; +} | { + "type": "rebranched"; + "id": string; + "from_step": string; + [key: string]: unknown; +} | { + "type": "created"; + "id": string; + [key: string]: unknown; +}); + +export type UseCaseElement = { + "id": string; + "text": string; +}; + +export type UseCaseElementDiff = { + "id": string; + "kind": "precondition" | "postcondition" | "step" | "branch"; + "changes": Array<"added" | "removed" | "edited" | "moved" | "rebranched">; + "old": (UseCaseDiffSide | null); + "new": (UseCaseDiffSide | null); + "text_diff"?: Array<{ + "op": "equal" | "delete" | "insert"; + "text": string; + [key: string]: unknown; +}>; + [key: string]: unknown; +}; + +export type UseCaseListRef = ("preconditions" | "main" | "postconditions" | { + "branch": string; + [key: string]: unknown; +}); + +export type UseCaseNewBranch = { + "id"?: (string | null); + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps"?: Array; +}; + +export type UseCaseNewElement = { + "id"?: (string | null); + "text": string; +}; + +export type UseCaseOperation = ({ + "op": "set_header"; + "title"?: (string | null); + "primary_actor"?: (string | null); + "secondary_actors"?: (Array | null); + "summary"?: (string | null); +} | { + "op": "add_element"; + "list": UseCaseListRef; + "after"?: (string | null); + "element": UseCaseNewElement; +} | { + "op": "update_text"; + "id": string; + "text": string; +} | { + "op": "move_element"; + "id": string; + "list"?: (UseCaseListRef | null); + "after"?: (string | null); +} | { + "op": "remove_element"; + "id": string; + "dependents"?: (UseCaseDependents | null); +} | { + "op": "add_branch"; + "branch": UseCaseNewBranch; +} | { + "op": "update_branch"; + "id": string; + "title"?: (string | null); + "kind"?: ("alternative" | "exception" | null); + "from_step"?: (string | null); + "end"?: (UseCaseBranchEnd | null); +} | { + "op": "remove_branch"; + "id": string; +}); + +export type UseCasePublishResponse = { + "version": use_cases_VersionView_response; + "removed_links": Array; + [key: string]: unknown; +}; + +export type entities_User_response = { + "id": number; + "username": string; + "name": string; + "email": string; + "creation_date": string; + "last_login": string; + "is_admin": boolean; + "email_verified": boolean; + [key: string]: unknown; +}; + +export type external_ProviderDiscovery_response = { + "password_enabled": boolean; + "external": Array; + [key: string]: unknown; +}; + +export type external_PublicAuthProvider_response = { + "id": string; + "display_name": string; + [key: string]: unknown; +}; + +export type use_cases_BaselineUseCaseView_response = { + "use_case_id": number; + "reference_code": string; + "title": string; + "primary_actor": (string | null); + "version_id": number; + "version_number": number; + "approval_state": string; + "current_version_id": (number | null); + "current_version_number": (number | null); + "link_count": number; + [key: string]: unknown; +}; + +export type use_cases_ClearSuspectResponse_response = { + "status": string; + "cleared": boolean; + [key: string]: unknown; +}; + +export type use_cases_DraftChangeView_response = { + "use_case_id": number; + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + "effects": Array; + [key: string]: unknown; +}; + +export type use_cases_DraftSummary_response = { + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + [key: string]: unknown; +}; + +export type use_cases_DraftView_response = { + "use_case_id": number; + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + [key: string]: unknown; +}; + +export type use_cases_LinkView_response = { + "id": number; + "element_id": (string | null); + "element_label": (string | null); + "target_type": "requirement" | "verification"; + "requirement_id": (number | null); + "verification_id": (number | null); + "target_reference_code": string; + "target_title": string; + "rationale": (string | null); + "suspect": boolean; + "suspect_at": (string | null); + "suspect_reason": (string | null); + "linked_requirement_version_id": (number | null); + "current_requirement_version_id": (number | null); + "created_by": (number | null); + "created_at": string; + [key: string]: unknown; +}; + +export type use_cases_NextCode_response = { + "reference_code": string; + [key: string]: unknown; +}; + +export type use_cases_PutDraftRequest_request = { + "base_revision"?: (number | null); + "document": UseCaseDocument; + [key: string]: unknown; +}; + +export type use_cases_SetApprovalRequest_request = { + "state": string; + [key: string]: unknown; +}; + +export type use_cases_UseCaseCommentView_response = { + "id": number; + "use_case_id": number; + "use_case_version_id": (number | null); + "element_id": (string | null); + "element_label": (string | null); + "author_id": number; + "author_name": string; + "body": string; + "created_at": string; + [key: string]: unknown; +}; + +export type use_cases_UseCaseDetail_response = { + "id": number; + "project_id": number; + "reference_code": string; + "title": string; + "created_by": (number | null); + "created_at": string; + "updated_at": string; + "current_version": (use_cases_VersionView_response | null); + "draft": (use_cases_DraftSummary_response | null); + [key: string]: unknown; +}; + +export type use_cases_UseCaseRef_response = { + "use_case_id": number; + "reference_code": string; + "title": string; + "link_id": number; + "element_id": (string | null); + "element_label": (string | null); + "suspect": boolean; + [key: string]: unknown; +}; + +export type use_cases_UseCaseRow_response = { + "id": number; + "reference_code": string; + "title": string; + "primary_actor": (string | null); + "step_count": number; + "alternative_count": number; + "exception_count": number; + "approval_state": (string | null); + "current_version_number": (number | null); + "has_draft": boolean; + "link_count": number; + "suspect_count": number; + "updated_at": string; + [key: string]: unknown; +}; + +export type use_cases_VersionSummary_response = { + "id": number; + "version_number": number; + "approval_state": string; + "title": string; + "primary_actor": (string | null); + "change_note": (string | null); + "created_by": (number | null); + "created_by_name": (string | null); + "created_at": string; + "reviewed_by": (number | null); + "reviewed_at": (string | null); + "approved_by": (number | null); + "approved_at": (string | null); + [key: string]: unknown; +}; + +export type use_cases_VersionView_response = { + "id": number; + "version_number": number; + "approval_state": string; + "title": string; + "primary_actor": (string | null); + "change_note": (string | null); + "created_by": (number | null); + "created_by_name": (string | null); + "created_at": string; + "reviewed_by": (number | null); + "reviewed_at": (string | null); + "approved_by": (number | null); + "approved_at": (string | null); + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + [key: string]: unknown; +}; diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index d4251a25..be9cc5c0 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -187,21 +187,9 @@ export interface DashboardPayload { csrf_token: string; } -export interface DeploymentInfo { - mode: 'server' | 'cloud' | string; - allows_self_registration: boolean; - requires_email_verification: boolean; - allows_admin_promotion: boolean; - assigns_personal_workspace: boolean; - allows_self_administered_user_creation: boolean; - /** False in cloud mode, where a whole-database dump would contain every tenant. */ - allows_database_backup?: boolean; -} +export type DeploymentInfo = import('./generated-contract').DeploymentInfo; -export interface AuthProviderDiscovery { - password_enabled: boolean; - external: Array<{ id: string; display_name: string }>; -} +export type AuthProviderDiscovery = import('./generated-contract').external_ProviderDiscovery_response; export interface ConnectedIdentity { id: number; @@ -229,12 +217,7 @@ export interface FrontendCompatibility { } /** Response from `GET /api/meta/build` (issue #213). */ -export interface BuildInfo { - backend_version: string; - backend_git_sha: string; - deployment_mode: string; - frontend_compatibility: FrontendCompatibility; -} +export type BuildInfo = import('./generated-contract').BuildInfo; export type RegistrationBody = { username: string; @@ -989,52 +972,16 @@ export interface UseCaseDocument { /** Display labels by element id: `3`, `A3`, `A3.1`, `E4`, `P1`, `Q1`. */ export type UseCaseLabels = Record; -export interface UseCaseRow { - id: number; - reference_code: string; - title: string; - primary_actor: string | null; - step_count: number; - alternative_count: number; - exception_count: number; - /** Of the current version; `null` before the first publish. */ - approval_state: string | null; - current_version_number: number | null; - has_draft: boolean; - /** Links to requirements and verifications, and how many are suspect (issue #400). */ - link_count: number; - suspect_count: number; - updated_at: string; -} +export type UseCaseRow = import('./generated-contract').use_cases_UseCaseRow_response; -export interface UseCaseVersionSummary { - id: number; - version_number: number; - approval_state: string; - title: string; - primary_actor: string | null; - change_note: string | null; - created_by: number | null; - created_by_name: string | null; - created_at: string; - reviewed_by: number | null; - reviewed_at: string | null; - approved_by: number | null; - approved_at: string | null; -} +export type UseCaseVersionSummary = import('./generated-contract').use_cases_VersionSummary_response; export interface UseCaseVersion extends UseCaseVersionSummary { document: UseCaseDocument; labels: UseCaseLabels; } -export interface UseCaseDraftSummary { - revision: number; - base_version_id: number | null; - updated_by: number | null; - updated_by_name: string | null; - updated_at: string; -} +export type UseCaseDraftSummary = import('./generated-contract').use_cases_DraftSummary_response; export interface UseCaseDraft extends UseCaseDraftSummary { use_case_id: number; @@ -1096,36 +1043,10 @@ export type UseCaseOperation = | { op: 'remove_branch'; id: string }; /** A link from a use case, or one of its elements, to a requirement or verification (issue #400). */ -export interface UseCaseLink { - id: number; - /** `null` for the whole use case. */ - element_id: string | null; - element_label: string | null; - target_type: 'requirement' | 'verification'; - requirement_id: number | null; - verification_id: number | null; - target_reference_code: string; - target_title: string; - rationale: string | null; - suspect: boolean; - suspect_at: string | null; - suspect_reason: string | null; - linked_requirement_version_id: number | null; - current_requirement_version_id: number | null; - created_by: number | null; - created_at: string; -} +export type UseCaseLink = import('./generated-contract').use_cases_LinkView_response; /** A use case that links to a requirement or verification. */ -export interface UseCaseRef { - use_case_id: number; - reference_code: string; - title: string; - link_id: number; - element_id: string | null; - element_label: string | null; - suspect: boolean; -} +export type UseCaseRef = import('./generated-contract').use_cases_UseCaseRef_response; /** Structural diff of two use case documents (issue #401). */ export type UseCaseChange = 'added' | 'removed' | 'edited' | 'moved' | 'rebranched'; @@ -1163,28 +1084,7 @@ export interface UseCaseDiff { elements: UseCaseElementDiff[]; } -export interface UseCaseComment { - id: number; - use_case_id: number; - use_case_version_id: number | null; - element_id: string | null; - element_label: string | null; - author_id: number; - author_name: string; - body: string; - created_at: string; -} +export type UseCaseComment = import('./generated-contract').use_cases_UseCaseCommentView_response; /** A use case version a baseline recorded (issue #402). */ -export interface BaselineUseCase { - use_case_id: number; - reference_code: string; - title: string; - primary_actor: string | null; - version_id: number; - version_number: number; - approval_state: string; - current_version_id: number | null; - current_version_number: number | null; - link_count: number; -} +export type BaselineUseCase = import('./generated-contract').use_cases_BaselineUseCaseView_response; diff --git a/frontend/src/hooks/__tests__/useBuildInfo.test.ts b/frontend/src/hooks/__tests__/useBuildInfo.test.ts index c35c5d11..1cedf393 100644 --- a/frontend/src/hooks/__tests__/useBuildInfo.test.ts +++ b/frontend/src/hooks/__tests__/useBuildInfo.test.ts @@ -7,7 +7,7 @@ vi.mock('@/api/client'); const build = { backend_version: '0.1.3', backend_git_sha: 'abc', - deployment_mode: 'server', + deployment_mode: 'server' as const, frontend_compatibility: { min_version: '0.1.0', max_version: '0.1.99' }, }; diff --git a/frontend/src/pages/__tests__/AccountPage.test.tsx b/frontend/src/pages/__tests__/AccountPage.test.tsx index 5138986b..79cd9fa6 100644 --- a/frontend/src/pages/__tests__/AccountPage.test.tsx +++ b/frontend/src/pages/__tests__/AccountPage.test.tsx @@ -31,6 +31,7 @@ const serverDeployment = { allows_admin_promotion: true, assigns_personal_workspace: false, allows_self_administered_user_creation: true, + allows_database_backup: true, }; function renderPage() { diff --git a/frontend/src/pages/__tests__/AdminPage.test.tsx b/frontend/src/pages/__tests__/AdminPage.test.tsx index 9fc82660..b935722e 100644 --- a/frontend/src/pages/__tests__/AdminPage.test.tsx +++ b/frontend/src/pages/__tests__/AdminPage.test.tsx @@ -47,6 +47,7 @@ const serverDeployment = { allows_admin_promotion: true, assigns_personal_workspace: false, allows_self_administered_user_creation: true, + allows_database_backup: true, }; const cloudDeployment = { @@ -55,6 +56,7 @@ const cloudDeployment = { allows_self_registration: true, allows_admin_promotion: false, allows_self_administered_user_creation: false, + allows_database_backup: false, }; function renderPage() { diff --git a/frontend/src/pages/__tests__/HelpPage.test.tsx b/frontend/src/pages/__tests__/HelpPage.test.tsx index 601f467b..1eae3b01 100644 --- a/frontend/src/pages/__tests__/HelpPage.test.tsx +++ b/frontend/src/pages/__tests__/HelpPage.test.tsx @@ -28,7 +28,7 @@ vi.mock('react-router-dom', async () => { const compatibleBuild = { backend_version: '0.1.0', backend_git_sha: '56c09392abcdef', - deployment_mode: 'server', + deployment_mode: 'server' as const, frontend_compatibility: { min_version: '0.1.0', max_version: '0.1.99' }, }; diff --git a/frontend/src/pages/__tests__/LoginPage.test.tsx b/frontend/src/pages/__tests__/LoginPage.test.tsx index 273c5aa5..59db6743 100644 --- a/frontend/src/pages/__tests__/LoginPage.test.tsx +++ b/frontend/src/pages/__tests__/LoginPage.test.tsx @@ -26,6 +26,7 @@ const serverDeployment = { allows_admin_promotion: true, assigns_personal_workspace: false, allows_self_administered_user_creation: true, + allows_database_backup: true, }; const cloudDeployment = { @@ -35,6 +36,7 @@ const cloudDeployment = { allows_admin_promotion: false, assigns_personal_workspace: true, allows_self_administered_user_creation: false, + allows_database_backup: false, }; describe('LoginPage', () => { diff --git a/marreq-cloud/Cargo.toml b/marreq-cloud/Cargo.toml index 52bd6a40..114fba30 100644 --- a/marreq-cloud/Cargo.toml +++ b/marreq-cloud/Cargo.toml @@ -31,6 +31,7 @@ anyhow = { workspace = true } sha2 = { workspace = true } [dev-dependencies] +regex = { workspace = true } marreq-core = { path = "../marreq-core", features = ["test-helpers"] } tokio = { workspace = true, features = ["rt"] } serde_json = { workspace = true } diff --git a/marreq-cloud/tests/api_contract_inventory.rs b/marreq-cloud/tests/api_contract_inventory.rs new file mode 100644 index 00000000..a0d6e8c5 --- /dev/null +++ b/marreq-cloud/tests/api_contract_inventory.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +//! Compare the compiled Rocket mount inventory with the OpenAPI-derived artifact. +//! No database or test-double routing: declared_mounts is also used by production. +use std::collections::BTreeSet; + +#[test] +fn compiled_server_and_cloud_routes_match_openapi_inventory() { + let expected: Vec = serde_json::from_str(include_str!( + "../../docs/developer/api-contract-routes.json" + )) + .expect("generated route inventory"); + let parameter = regex::Regex::new(r"<([\w]+)(?:\.\.)?>").unwrap(); + for (variant, extra) in [ + ("server", Vec::new()), + ("cloud", marreq_cloud::routes::routes()), + ] { + let mounts = marreq_core::app::declared_mounts(extra); + let actual: BTreeSet<_> = [("/api", mounts.api), ("", mounts.root)] + .into_iter() + .flat_map(|(base, routes)| { + let parameter = ¶meter; + routes.into_iter().filter_map(move |(_, route)| { + let path = route.uri.path().to_owned(); + // Origin root UI and internal CSRF denial routes are not + // HTTP consumer APIs. OAuth routes under root are included. + if base.is_empty() + && !(path.starts_with("/oauth/") + || path.starts_with("/.well-known/") + || path.starts_with("/api/")) + { + return None; + } + let normalized = parameter.replace_all(&path, "{$1}"); + Some((route.method.to_string(), format!("{base}{normalized}"))) + }) + }) + .collect(); + let documented: BTreeSet<_> = expected + .iter() + .filter(|op| { + op["variants"] + .as_array() + .unwrap() + .iter() + .any(|v| v == variant) + }) + .map(|op| { + ( + op["method"].as_str().unwrap().to_owned(), + op["path"].as_str().unwrap().to_owned(), + ) + }) + .collect(); + assert_eq!(actual, documented, "{variant} route contract drift"); + } +} diff --git a/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs b/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs index f76697e3..946cb674 100644 --- a/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs +++ b/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs @@ -85,10 +85,12 @@ async fn status_of( body: Option, key: Option<&str>, ) -> (Status, String) { + let recorded_body = body.clone(); let mut request = match method { "GET" => client.get(path.to_owned()), "POST" => client.post(path.to_owned()), "DELETE" => client.delete(path.to_owned()), + "PUT" => client.put(path.to_owned()), other => panic!("unsupported method {other}"), } .header(bearer(token)); @@ -100,7 +102,22 @@ async fn status_of( } let response = request.dispatch().await; let status = response.status(); - (status, response.into_string().await.unwrap_or_default()) + let text = response.into_string().await.unwrap_or_default(); + if let Ok(output) = std::env::var("MARREQ_CONTRACT_TRANSCRIPT") { + use std::io::Write; + let record = serde_json::json!({ + "method": method, "path": path, "status": status.code, + "request": recorded_body, + "response": serde_json::from_str::(&text).unwrap_or(serde_json::Value::Null), + }); + let mut file = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(output) + .expect("contract transcript"); + writeln!(file, "{record}").expect("record actual Rocket exchange"); + } + (status, text) } #[rocket::async_test] @@ -155,6 +172,8 @@ async fn delegated_scopes_gate_the_routes_wrapped_by_mcp_tools() { for (raw, scopes) in [ ("projects", vec!["projects:read"]), + ("uc-read", vec!["use_cases:read"]), + ("uc-write", vec!["use_cases:read", "use_cases:write"]), ("req-read", vec!["requirements:read"]), ("trace-read", vec!["traceability:read"]), ("trace-write", vec!["traceability:write"]), @@ -174,6 +193,48 @@ async fn delegated_scopes_gate_the_routes_wrapped_by_mcp_tools() { ); let client = Client::tracked(rocket).await.expect("Rocket client"); + // Contract verification uses the real Rocket handlers and migrated PostgreSQL. + // Credentials never enter the transcript; only synthetic payloads are recorded. + for path in ["/api/meta/build", "/api/meta/deployment"] { + let (status, _) = status_of(&client, "GET", path, "uc-read", None, None).await; + assert_eq!(status, Status::Ok); + } + let use_cases = format!("{RES_BASE}/use-cases"); + let (status, _) = status_of(&client, "GET", &use_cases, "req-read", None, None).await; + assert_eq!(status, Status::Forbidden); + let (status, created) = status_of( + &client, + "POST", + &use_cases, + "uc-write", + Some(serde_json::json!({"title": "Contract use case", "primary_actor": "Tester"})), + Some("contract-create-use-case"), + ) + .await; + assert_eq!(status, Status::Created, "{created}"); + let created: serde_json::Value = serde_json::from_str(&created).unwrap(); + let uc_id = created["id"].as_i64().unwrap(); + let revision = created["draft"]["revision"].as_i64().unwrap(); + let draft_path = format!("{use_cases}/{uc_id}/draft"); + let document = serde_json::json!({ + "schema": "marreq.use-case.v1", "title": "Contract use case", + "main": [{"id": "s_test1", "text": "Verify the API contract"}] + }); + let (status, body) = status_of( + &client, + "PUT", + &draft_path, + "uc-write", + Some(serde_json::json!({"base_revision": revision, "document": document})), + None, + ) + .await; + assert_eq!(status, Status::Ok, "{body}"); + let (status, _) = status_of(&client, "GET", &use_cases, "uc-read", None, None).await; + assert_eq!(status, Status::Ok); + let (status, _) = status_of(&client, "GET", &draft_path, "uc-read", None, None).await; + assert_eq!(status, Status::Ok); + // (method, path, token lacking the scope, token holding it, expected status with scope) let links = format!("{RES_BASE}/requirement-version-links"); let reads: Vec<(String, &str, &str, Status)> = vec![ diff --git a/mcp-server/Dockerfile b/mcp-server/Dockerfile index 08e905f7..1cafae39 100644 --- a/mcp-server/Dockerfile +++ b/mcp-server/Dockerfile @@ -13,6 +13,8 @@ COPY --from=build --chown=node:node /app/package.json ./ COPY --from=build --chown=node:node /app/node_modules ./node_modules COPY --from=build --chown=node:node /app/dist ./dist COPY --chown=node:node mcp-server/MARREQ_SKILL.md ./MARREQ_SKILL.md +COPY --chown=node:node mcp-server/compatibility.json ./compatibility.json +COPY docs/developer/openapi.yaml /app/contracts/openapi.yaml USER node EXPOSE 3000 HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \ diff --git a/mcp-server/compatibility.json b/mcp-server/compatibility.json new file mode 100644 index 00000000..776ee06e --- /dev/null +++ b/mcp-server/compatibility.json @@ -0,0 +1,4 @@ +{ + "requires_backend_min": "0.1.0", + "requires_backend_max": "0.2.99" +} diff --git a/mcp-server/src/client.ts b/mcp-server/src/client.ts index 77797c95..6e083172 100644 --- a/mcp-server/src/client.ts +++ b/mcp-server/src/client.ts @@ -1,3 +1,10 @@ +import type { + McpPrincipalResponse, + use_cases_UseCaseRow_response as UseCaseRow, + use_cases_UseCaseDetail_response as UseCaseDetail, + use_cases_DraftView_response as UseCaseDraft, + use_cases_DraftChangeView_response as UseCaseDraftChange, +} from "./generated-contract.js"; import type { SessionContext } from "./context.js"; import { currentBearer } from "./credentials.js"; @@ -18,13 +25,7 @@ export class MarreqClient { return this.request("/api/projects"); } - async getPrincipal(): Promise<{ - user_id: number; - authentication_type: string; - principal_id: string; - client_id?: string; - grant_id?: number; - }> { + async getPrincipal(): Promise { return this.request("/api/mcp/principal"); } @@ -360,7 +361,7 @@ export class MarreqClient { return `/api/projects/${this.ctx.projectId}/use-cases${path}`; } - async listUseCases(query: { q?: string; approval_state?: string; has_suspect?: boolean } = {}) { + async listUseCases(query: { q?: string; approval_state?: string; has_suspect?: boolean } = {}): Promise { const params = new URLSearchParams(); if (query.q) params.set("q", query.q); if (query.approval_state) params.set("approval_state", query.approval_state); @@ -369,14 +370,14 @@ export class MarreqClient { return this.request(this.useCases(q ? `?${q}` : "")); } - async getUseCase(id: number, versionId?: number) { + async getUseCase(id: number, versionId?: number): Promise { return this.request(this.useCases(`/${id}${versionId != null ? `?version_id=${versionId}` : ""}`)); } /** The shared draft, or null when there is none. */ - async getUseCaseDraft(id: number) { + async getUseCaseDraft(id: number): Promise { try { - return await this.request(this.useCases(`/${id}/draft`)); + return await this.request(this.useCases(`/${id}/draft`)); } catch (e) { if (e instanceof Error && e.message.startsWith("Marreq API 404")) return null; throw e; @@ -399,7 +400,7 @@ export class MarreqClient { payload: { reference_code?: string; title?: string; primary_actor?: string; document?: unknown }, idempotencyKey: string ) { - return this.request(this.useCases(), { + return this.request(this.useCases(), { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(payload), @@ -413,7 +414,7 @@ export class MarreqClient { }); } - async putUseCaseDraft(id: number, baseRevision: number | null, document: unknown) { + async putUseCaseDraft(id: number, baseRevision: number | null, document: unknown): Promise { return this.request(this.useCases(`/${id}/draft`), { method: "PUT", body: JSON.stringify({ base_revision: baseRevision, document }), diff --git a/mcp-server/src/generated-contract.ts b/mcp-server/src/generated-contract.ts new file mode 100644 index 00000000..5e16cda6 --- /dev/null +++ b/mcp-server/src/generated-contract.ts @@ -0,0 +1,450 @@ +// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml. +// Do not edit; this file carries wire types, not a second contract. + +export type ApiError = { + "status": number; + "error": string; + "message": string; + [key: string]: unknown; +}; + +export type BuildInfo = { + "backend_version": string; + "backend_git_sha": string; + "deployment_mode": "server" | "cloud"; + "frontend_compatibility": { + "min_version": string; + "max_version": string; + [key: string]: unknown; +}; + [key: string]: unknown; +}; + +export type DeploymentInfo = { + "mode": "server" | "cloud"; + "allows_self_registration": boolean; + "requires_email_verification": boolean; + "allows_admin_promotion": boolean; + "assigns_personal_workspace": boolean; + "allows_self_administered_user_creation": boolean; + "allows_database_backup": boolean; + [key: string]: unknown; +}; + +export type McpPrincipalResponse = { + "user_id": number; + "authentication_type": string; + "principal_id": string; + "client_id": (string | null); + "grant_id": (number | null); + [key: string]: unknown; +}; + +export type UseCaseBranch = { + "id": string; + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps"?: Array; +}; + +export type UseCaseBranchEnd = ({ + "type": "rejoin"; + "step": string; +} | { + "type": "end"; + "outcome": "success" | "failure"; +}); + +export type UseCaseBranchResponse = { + "id": string; + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps": Array; +}; + +export type UseCaseDependents = ("cascade" | { + "rebranch_to": string; + [key: string]: unknown; +}); + +export type UseCaseDiff = { + "from": (UseCaseDiffEnd | null); + "to": UseCaseDiffEnd; + "summary": { + "added": number; + "removed": number; + "edited": number; + "moved": number; + "rebranched": number; + [key: string]: unknown; +}; + "header": { + [key: string]: { + "old": (string | null | Array); + "new": (string | null | Array); + [key: string]: unknown; +}; +}; + "elements": Array; + [key: string]: unknown; +}; + +export type UseCaseDiffEnd = { + "version_id": (number | null); + "version_number": (number | null); + "draft_revision": (number | null); + [key: string]: unknown; +}; + +export type UseCaseDiffSide = { + "list": "preconditions" | "main" | "postconditions" | "branch" | "branches"; + "branch_id"?: string; + "label": (string | null); + "text": string; + "from_step"?: string; + "end"?: UseCaseBranchEnd; + "kind"?: "alternative" | "exception"; + [key: string]: unknown; +}; + +export type UseCaseDocument = { + "schema"?: "marreq.use-case.v1"; + "title": string; + "primary_actor"?: (string | null); + "secondary_actors"?: Array; + "summary"?: (string | null); + "preconditions"?: Array; + "main"?: Array; + "branches"?: Array; + "postconditions"?: Array; +}; + +export type UseCaseDocumentResponse = { + "schema": "marreq.use-case.v1"; + "title": string; + "primary_actor": (string | null); + "secondary_actors": Array; + "summary": (string | null); + "preconditions": Array; + "main": Array; + "branches": Array; + "postconditions": Array; +}; + +export type UseCaseEffect = ({ + "type": "relabelled"; + "id": string; + "from": string; + "to": string; + [key: string]: unknown; +} | { + "type": "removed"; + "id": string; + [key: string]: unknown; +} | { + "type": "rebranched"; + "id": string; + "from_step": string; + [key: string]: unknown; +} | { + "type": "created"; + "id": string; + [key: string]: unknown; +}); + +export type UseCaseElement = { + "id": string; + "text": string; +}; + +export type UseCaseElementDiff = { + "id": string; + "kind": "precondition" | "postcondition" | "step" | "branch"; + "changes": Array<"added" | "removed" | "edited" | "moved" | "rebranched">; + "old": (UseCaseDiffSide | null); + "new": (UseCaseDiffSide | null); + "text_diff"?: Array<{ + "op": "equal" | "delete" | "insert"; + "text": string; + [key: string]: unknown; +}>; + [key: string]: unknown; +}; + +export type UseCaseListRef = ("preconditions" | "main" | "postconditions" | { + "branch": string; + [key: string]: unknown; +}); + +export type UseCaseNewBranch = { + "id"?: (string | null); + "kind": "alternative" | "exception"; + "title": string; + "from_step": string; + "end": UseCaseBranchEnd; + "steps"?: Array; +}; + +export type UseCaseNewElement = { + "id"?: (string | null); + "text": string; +}; + +export type UseCaseOperation = ({ + "op": "set_header"; + "title"?: (string | null); + "primary_actor"?: (string | null); + "secondary_actors"?: (Array | null); + "summary"?: (string | null); +} | { + "op": "add_element"; + "list": UseCaseListRef; + "after"?: (string | null); + "element": UseCaseNewElement; +} | { + "op": "update_text"; + "id": string; + "text": string; +} | { + "op": "move_element"; + "id": string; + "list"?: (UseCaseListRef | null); + "after"?: (string | null); +} | { + "op": "remove_element"; + "id": string; + "dependents"?: (UseCaseDependents | null); +} | { + "op": "add_branch"; + "branch": UseCaseNewBranch; +} | { + "op": "update_branch"; + "id": string; + "title"?: (string | null); + "kind"?: ("alternative" | "exception" | null); + "from_step"?: (string | null); + "end"?: (UseCaseBranchEnd | null); +} | { + "op": "remove_branch"; + "id": string; +}); + +export type UseCasePublishResponse = { + "version": use_cases_VersionView_response; + "removed_links": Array; + [key: string]: unknown; +}; + +export type entities_User_response = { + "id": number; + "username": string; + "name": string; + "email": string; + "creation_date": string; + "last_login": string; + "is_admin": boolean; + "email_verified": boolean; + [key: string]: unknown; +}; + +export type external_ProviderDiscovery_response = { + "password_enabled": boolean; + "external": Array; + [key: string]: unknown; +}; + +export type external_PublicAuthProvider_response = { + "id": string; + "display_name": string; + [key: string]: unknown; +}; + +export type use_cases_BaselineUseCaseView_response = { + "use_case_id": number; + "reference_code": string; + "title": string; + "primary_actor": (string | null); + "version_id": number; + "version_number": number; + "approval_state": string; + "current_version_id": (number | null); + "current_version_number": (number | null); + "link_count": number; + [key: string]: unknown; +}; + +export type use_cases_ClearSuspectResponse_response = { + "status": string; + "cleared": boolean; + [key: string]: unknown; +}; + +export type use_cases_DraftChangeView_response = { + "use_case_id": number; + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + "effects": Array; + [key: string]: unknown; +}; + +export type use_cases_DraftSummary_response = { + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + [key: string]: unknown; +}; + +export type use_cases_DraftView_response = { + "use_case_id": number; + "revision": number; + "base_version_id": (number | null); + "updated_by": (number | null); + "updated_by_name": (string | null); + "updated_at": string; + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + [key: string]: unknown; +}; + +export type use_cases_LinkView_response = { + "id": number; + "element_id": (string | null); + "element_label": (string | null); + "target_type": "requirement" | "verification"; + "requirement_id": (number | null); + "verification_id": (number | null); + "target_reference_code": string; + "target_title": string; + "rationale": (string | null); + "suspect": boolean; + "suspect_at": (string | null); + "suspect_reason": (string | null); + "linked_requirement_version_id": (number | null); + "current_requirement_version_id": (number | null); + "created_by": (number | null); + "created_at": string; + [key: string]: unknown; +}; + +export type use_cases_NextCode_response = { + "reference_code": string; + [key: string]: unknown; +}; + +export type use_cases_PutDraftRequest_request = { + "base_revision"?: (number | null); + "document": UseCaseDocument; + [key: string]: unknown; +}; + +export type use_cases_SetApprovalRequest_request = { + "state": string; + [key: string]: unknown; +}; + +export type use_cases_UseCaseCommentView_response = { + "id": number; + "use_case_id": number; + "use_case_version_id": (number | null); + "element_id": (string | null); + "element_label": (string | null); + "author_id": number; + "author_name": string; + "body": string; + "created_at": string; + [key: string]: unknown; +}; + +export type use_cases_UseCaseDetail_response = { + "id": number; + "project_id": number; + "reference_code": string; + "title": string; + "created_by": (number | null); + "created_at": string; + "updated_at": string; + "current_version": (use_cases_VersionView_response | null); + "draft": (use_cases_DraftSummary_response | null); + [key: string]: unknown; +}; + +export type use_cases_UseCaseRef_response = { + "use_case_id": number; + "reference_code": string; + "title": string; + "link_id": number; + "element_id": (string | null); + "element_label": (string | null); + "suspect": boolean; + [key: string]: unknown; +}; + +export type use_cases_UseCaseRow_response = { + "id": number; + "reference_code": string; + "title": string; + "primary_actor": (string | null); + "step_count": number; + "alternative_count": number; + "exception_count": number; + "approval_state": (string | null); + "current_version_number": (number | null); + "has_draft": boolean; + "link_count": number; + "suspect_count": number; + "updated_at": string; + [key: string]: unknown; +}; + +export type use_cases_VersionSummary_response = { + "id": number; + "version_number": number; + "approval_state": string; + "title": string; + "primary_actor": (string | null); + "change_note": (string | null); + "created_by": (number | null); + "created_by_name": (string | null); + "created_at": string; + "reviewed_by": (number | null); + "reviewed_at": (string | null); + "approved_by": (number | null); + "approved_at": (string | null); + [key: string]: unknown; +}; + +export type use_cases_VersionView_response = { + "id": number; + "version_number": number; + "approval_state": string; + "title": string; + "primary_actor": (string | null); + "change_note": (string | null); + "created_by": (number | null); + "created_by_name": (string | null); + "created_at": string; + "reviewed_by": (number | null); + "reviewed_at": (string | null); + "approved_by": (number | null); + "approved_at": (string | null); + "document": UseCaseDocumentResponse; + "labels": { + [key: string]: string; +}; + [key: string]: unknown; +}; diff --git a/mcp-server/src/remote.ts b/mcp-server/src/remote.ts index f2ed51cc..4d517c49 100644 --- a/mcp-server/src/remote.ts +++ b/mcp-server/src/remote.ts @@ -1,3 +1,4 @@ +import type { McpPrincipalResponse } from "./generated-contract.js"; import { randomUUID } from "node:crypto"; import type { Server } from "node:http"; import type { Request, Response } from "express"; @@ -9,13 +10,7 @@ import { loadContext, type SessionContext } from "./context.js"; import { MarreqAuthenticationError, MarreqClient } from "./client.js"; import { withBearer } from "./credentials.js"; -interface Principal { - user_id: number; - authentication_type: string; - principal_id: string; - client_id?: string; - grant_id?: number; -} +type Principal = McpPrincipalResponse; type PrincipalResolution = | { kind: "ok"; principal: Principal } diff --git a/scripts/check-api-compatibility.sh b/scripts/check-api-compatibility.sh new file mode 100755 index 00000000..b749d780 --- /dev/null +++ b/scripts/check-api-compatibility.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exec python3 "$(dirname "$0")/contracts/compatibility.py" "$@" diff --git a/scripts/contracts/bundle.py b/scripts/contracts/bundle.py new file mode 100644 index 00000000..0414620e --- /dev/null +++ b/scripts/contracts/bundle.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Export the exact checked-out component's contract for retention with its image. + +This records evidence; deployment verification still rejects unverified operations. +Never use this to manufacture contracts for older releases from current sources. +""" +import argparse +import json +import subprocess +import sys + +from compatibility import ROOT, ContractError, digest, load, policy, version +from check import check + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--component", choices=["backend", "frontend", "mcp"], required=True) + parser.add_argument("--version", required=True) + parser.add_argument("--variant", choices=["server", "cloud"]) + parser.add_argument("--output", required=True) + args = parser.parse_args() + version(args.version) + errors, gaps = check() + if errors: + raise ContractError("; ".join(errors)) + if args.component == "backend" and not args.variant: + raise ContractError("backend bundle requires --variant") + sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() + # Verify tracked contract inputs match the commit whose identity is exported. + paths = list(load(ROOT / "docs/developer/api-contract-source-review.json")) + ["docs/developer/openapi.yaml", "docs/developer/api-contract-source-review.json"] + tracked = subprocess.run(["git", "ls-files", "--error-unmatch", "--", *paths], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + if tracked.returncode: + raise ContractError("commit all audited source files before exporting a source-bound bundle") + result = subprocess.run(["git", "diff", "--quiet", "HEAD", "--", *paths], cwd=ROOT) + if result.returncode: + raise ContractError("commit audited contract inputs before exporting a source-bound bundle") + contract = load(ROOT / "docs/developer/openapi.yaml") + # Until consumer call coverage is complete, use the entire backend surface. + # This is conservative and documented; never silently omit dependencies. + bundle = {"format": "marreq.contract.v1", "component": args.component, "version": args.version, "source_sha": sha, "contract": contract} + if args.component == "backend": + bundle.update(variant=args.variant, frontend_compatibility=load(ROOT / "marreq-core/frontend_compatibility.json")) + else: + directory = "mcp-server" if args.component == "mcp" else "frontend" + bundle["compatibility"] = load(ROOT / directory / "compatibility.json") + policy(bundle["compatibility"]) + from pathlib import Path + Path(args.output).write_text(json.dumps(bundle, indent=2) + "\n") + print(json.dumps({"file": args.output, "sha256": digest(args.output), "version": args.version, "source_sha": sha})) + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (ContractError, OSError, ValueError) as error: + print(str(error), file=sys.stderr) + sys.exit(1) diff --git a/scripts/contracts/check.py b/scripts/contracts/check.py new file mode 100644 index 00000000..8b98ceeb --- /dev/null +++ b/scripts/contracts/check.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Validate OpenAPI, route coverage, compatibility metadata, and review evidence.""" +import argparse +import hashlib +import json +import sys + +from openapi_spec_validator import validate +from compatibility import ROOT, ContractError, load, policy, version, resolve +from generate import generated +from inventory import source_routes + + +def source_evidence(): + files = set() + for directory in ("marreq-core/src", "marreq-server/src", "marreq-cloud/src", "frontend/src/api", "mcp-server/src"): + files.update(file for file in (ROOT / directory).rglob("*") if file.suffix in (".ts", ".rs") and "__tests__" not in file.parts and not file.name.endswith(".test.ts") and file.name != "generated-contract.ts") + # Direct frontend calls outside the centralized API modules are still + # dependencies, including binary downloads/imports and OAuth navigation. + for file in (ROOT / "frontend/src").rglob("*"): + if file.suffix in (".ts", ".tsx") and "__tests__" not in file.parts: + content = file.read_text() + if "/api/" in content or "fetch(" in content: + files.add(file) + files.update(ROOT / name for name in ("marreq-core/src/app.rs", "marreq-core/src/build_info.rs", "marreq-core/src/api/mod.rs", "marreq-cloud/src/routes.rs", "marreq-cloud/src/deployment.rs", "marreq-server/src/deployment.rs", "marreq-server/src/routes.rs", "marreq-server/src/main.rs", "marreq-cloud/src/main.rs", "frontend/compatibility.json", "marreq-core/frontend_compatibility.json", "mcp-server/compatibility.json")) + return {str(file.relative_to(ROOT)): hashlib.sha256(file.read_bytes()).hexdigest() for file in sorted(files) if file.is_file()} + + +def check(require_complete=False): + spec = load(ROOT / "docs/developer/openapi.yaml") + resolve(spec, spec) + validate(spec) + for component in ("frontend", "mcp-server"): + policy(load(ROOT / component / "compatibility.json")) + backend_range = load(ROOT / "marreq-core/frontend_compatibility.json") + if set(backend_range) != {"min_version", "max_version"} or version(backend_range["min_version"]) > version(backend_range["max_version"]): + raise ContractError("invalid backend frontend compatibility range") + errors = [] + source = {(r["path"], r["method"]): r for r in source_routes()} + documented = {(p, m): op for p, item in spec["paths"].items() for m, op in item.items()} + for key in source.keys() - documented.keys(): + errors.append(f"undocumented backend route: {key}") + for key in documented.keys() - source.keys(): + errors.append(f"documented route missing in backend: {key}") + for key in source.keys() & documented.keys(): + variants = [source[key]["variant"]] if source[key]["variant"] != "shared" else ["server", "cloud"] + if documented[key].get("x-marreq-variants") != variants: + errors.append(f"deployment variant mismatch: {key}") + if documented[key].get("x-marreq-guards") != source[key]["guards"]: + errors.append(f"authentication guard mismatch: {key}") + if documented[key].get("x-marreq-handler") != source[key]["handler"]: + errors.append(f"handler mismatch: {key}") + for path, content in generated(spec).items(): + if not path.exists() or path.read_text() != content: + errors.append(f"generated artifact drift: {path.relative_to(ROOT)}; run scripts/contracts/generate.py") + lock = ROOT / "docs/developer/api-contract-source-review.json" + if not lock.exists() or load(lock) != source_evidence(): + errors.append("API source changed since contract review. Audit wire behavior, update OpenAPI/tests, then run check.py --record-source-review. This acknowledgement is not a substitute for schema or integration tests.") + gaps = [f"{m.upper()} {p}" for (p, m), op in documented.items() if op.get("x-marreq-unverified")] + if require_complete and spec.get("x-marreq-contract-complete") is not True: + errors.append("producer contract is explicitly incomplete; finish the P0 audit before publication") + if require_complete and gaps: + errors.extend(f"unverified wire contract: {gap}" for gap in gaps) + return errors, gaps + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--record-source-review", action="store_true", help="Explicitly acknowledge source audit; review resulting diff") + parser.add_argument("--require-complete", action="store_true", help="Release safety: fail on inventory-only operations") + args = parser.parse_args() + if args.record_source_review: + (ROOT / "docs/developer/api-contract-source-review.json").write_text(json.dumps(source_evidence(), indent=2) + "\n") + errors, gaps = check(args.require_complete) + print(json.dumps({"valid": not errors, "unverified_operations": len(gaps), "errors": errors}, indent=2)) + return 1 if errors else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/contracts/check_baseline.py b/scripts/contracts/check_baseline.py new file mode 100644 index 00000000..4c34a673 --- /dev/null +++ b/scripts/contracts/check_baseline.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Reject breaking changes to audited operations in the PR base contract.""" +import copy +import json +from pathlib import Path +import sys + +from compatibility import ROOT, compare, load + + +def check(previous, proposed): + if "x-marreq-contract-complete" not in previous: + # The pre-bootstrap document is explicitly partial and has a different + # /api server base. It is not evidence for a historical consumer release. + return {"compatible": None, "bootstrap": True, "errors": [], "reason": "Previous OpenAPI predates verified contract inventory; no historical compatibility established."} + audited = copy.deepcopy(previous) + audited["paths"] = {path: {method: op for method, op in item.items() if not op.get("x-marreq-unverified")} for path, item in previous["paths"].items()} + audited["paths"] = {path: item for path, item in audited["paths"].items() if item} + errors = sorted(set(error for variant in ("server", "cloud") for error in compare(audited, proposed, variant))) + return {"compatible": not errors, "errors": errors} + + +if __name__ == "__main__": + result = check(load(Path(sys.argv[1])), load(ROOT / "docs/developer/openapi.yaml")) + print(json.dumps(result, indent=2)) + sys.exit(1 if result["errors"] else 0) diff --git a/scripts/contracts/compatibility.py b/scripts/contracts/compatibility.py new file mode 100644 index 00000000..ef89be12 --- /dev/null +++ b/scripts/contracts/compatibility.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +"""Fail-closed, offline compatibility checks for immutable component bundles. + +The deployment plan is an input, never inferred from package versions or main. +Only local files are read; artifact downloading/authentication belongs to CI/CD. +""" +from __future__ import annotations + +import argparse +import copy +import hashlib +import json +from pathlib import Path +import re +import sys + +import yaml +from openapi_spec_validator import validate as validate_spec + +ROOT = Path(__file__).resolve().parents[2] +METHODS = {"get", "post", "put", "patch", "delete", "head", "options"} + + +class ContractError(ValueError): + pass + + +def load(path): + with Path(path).open() as stream: + return yaml.safe_load(stream) + + +def version(value): + # No coercion, wildcards, prereleases, or lexicographic comparison. Release + # candidates need their own explicitly verified deployment plan. + if not isinstance(value, str) or not re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)", value): + raise ContractError(f"expected stable SemVer X.Y.Z, got {value!r}") + return tuple(map(int, value.split("."))) + + +def policy(value): + if not isinstance(value, dict) or set(value) - {"requires_backend_min", "requires_backend_max"}: + raise ContractError("invalid compatibility metadata fields") + if "requires_backend_min" not in value: + raise ContractError("compatibility metadata requires requires_backend_min") + low = version(value["requires_backend_min"]) + high = version(value["requires_backend_max"]) if "requires_backend_max" in value else None + if high is not None and low > high: + raise ContractError("compatibility minimum exceeds maximum") + return low, high + + +def resolve(node, document, seen=()): + """Expand local refs; reject external refs and recursive schemas conservatively.""" + if isinstance(node, list): + return [resolve(item, document, seen) for item in node] + if not isinstance(node, dict): + return node + if "$ref" in node: + ref = node["$ref"] + if not ref.startswith("#/") or ref in seen: + raise ContractError(f"unsupported external or recursive reference: {ref}") + target = document + for key in ref[2:].split("/"): + target = target[key.replace("~1", "/").replace("~0", "~")] + target = copy.deepcopy(target) + target.update({key: value for key, value in node.items() if key != "$ref"}) + return resolve(target, document, (*seen, ref)) + return {key: resolve(value, document, seen) for key, value in node.items()} + + +def subset(source, target, location): + """Prove every source value is accepted by target, or fail closed. + + Requests: released consumer -> proposed producer. + Responses: proposed producer -> released consumer. + Unsupported changed constraints cannot accidentally establish compatibility. + """ + if source == target or target is True or target == {} or source is False: + return [] + if target is False or source is True: + return [f"{location}: schema acceptance changed"] + if not isinstance(source, dict) or not isinstance(target, dict): + return [f"{location}: unsupported schema change"] + ignored = {"title", "description", "examples", "example", "deprecated", "default", "readOnly", "writeOnly", "$schema", "x-rust-model"} + supported = {"type", "enum", "const", "properties", "required", "items", "additionalProperties", "minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems", "minProperties", "maxProperties"} + unknown = (set(source) | set(target)) - supported - ignored + errors = [] + for key in sorted(unknown): + if source.get(key) != target.get(key): + errors.append(f"{location}: changed {key} needs explicit compatibility evidence") + def types(schema): + value = schema.get("type", []) + return set(value if isinstance(value, list) else [value]) + a, b = types(source), types(target) + if b and (not a or any(t not in b and not (t == "integer" and "number" in b) for t in a)): + errors.append(f"{location}: type {sorted(a)} is not accepted by {sorted(b)}") + for key in ("enum", "const"): + if key in target: + candidates = source.get("enum", [source["const"]] if "const" in source else None) + accepted = target[key] if key == "enum" else [target[key]] + if candidates is None or any(item not in accepted for item in candidates): + errors.append(f"{location}: incompatible {key}") + for key in ("minimum", "minLength", "minItems", "minProperties"): + if key in target and (key not in source or source[key] < target[key]): + errors.append(f"{location}: tightened {key}") + for key in ("maximum", "maxLength", "maxItems", "maxProperties"): + if key in target and (key not in source or source[key] > target[key]): + errors.append(f"{location}: tightened {key}") + for key in sorted(set(target.get("required", [])) - set(source.get("required", []))): + errors.append(f"{location}.{key}: required property is not guaranteed") + source_props = source.get("properties", {}) + target_props = target.get("properties", {}) + source_extra = source.get("additionalProperties", True) + target_extra = target.get("additionalProperties", True) + for key in sorted(set(source_props) | set(target_props)): + left = source_props.get(key, source_extra) + right = target_props.get(key, target_extra) + if left is not False: + errors.extend(subset(left, right, f"{location}.{key}")) + errors.extend([] if source_extra is False else subset(source_extra, target_extra, f"{location}.*")) + if "items" in source or "items" in target: + errors.extend(subset(source.get("items", True), target.get("items", True), f"{location}[]")) + return errors + + +def operations(spec, variant): + result = {} + for path, item in spec["paths"].items(): + for method in METHODS & item.keys(): + op = item[method] + if variant in op.get("x-marreq-variants", ["server", "cloud"]): + result[(path, method)] = {**op, "parameters": item.get("parameters", []) + op.get("parameters", [])} + return result + + +def compare(consumer, backend, variant): + consumer_resolved = resolve(consumer, consumer) + backend_resolved = resolve(backend, backend) + validate_spec(consumer) + validate_spec(backend) + required = operations(consumer_resolved, variant) + offered = operations(backend_resolved, variant) + errors = [] + for key, old in required.items(): + path, method = key + at = f"{method.upper()} {path}" + if key not in offered: + errors.append(f"{at}: endpoint missing in {variant} backend") + continue + new = offered[key] + for op in (old, new): + if op.get("x-marreq-unverified"): + errors.append(f"{at}: contract has unverified behavior; compatibility cannot be established") + old_security = old.get("security", consumer.get("security", [])) + new_security = new.get("security", backend.get("security", [])) + if old_security != new_security: + errors.append(f"{at}: authentication/scopes changed") + if consumer.get("components", {}).get("securitySchemes") != backend.get("components", {}).get("securitySchemes"): + errors.append(f"{at}: security scheme definitions changed") + for name in ("x-marreq-policy", "x-marreq-csrf", "x-marreq-bearer-scopes"): + if old.get(name) != new.get(name): + errors.append(f"{at}: {name} changed") + old_params = {(p["in"], p["name"]): p for p in old.get("parameters", [])} + new_params = {(p["in"], p["name"]): p for p in new.get("parameters", [])} + for p, param in new_params.items(): + if p not in old_params: + if param.get("required"): + errors.append(f"{at}: new required {p} parameter") + else: + if param.get("required") and not old_params[p].get("required"): + errors.append(f"{at}: {p} parameter became required") + errors.extend(subset(old_params[p].get("schema", {}), param.get("schema", {}), f"{at} parameter {p}")) + if set(old_params) - set(new_params): + errors.append(f"{at}: accepted parameters removed") + old_body, new_body = old.get("requestBody", {}), new.get("requestBody", {}) + if new_body.get("required") and not old_body.get("required"): + errors.append(f"{at}: request body became required") + for media, content in old_body.get("content", {}).items(): + if media not in new_body.get("content", {}): + errors.append(f"{at}: request media {media} removed") + else: + errors.extend(subset(content.get("schema", {}), new_body["content"][media].get("schema", {}), f"{at} request")) + old_responses, new_responses = old["responses"], new["responses"] + # Even error status changes matter: clients branch on 401/403/404/409. + if old_responses.keys() != new_responses.keys(): + errors.append(f"{at}: response status codes changed") + for status in old_responses.keys() & new_responses.keys(): + previous = old_responses[status] + proposed = new_responses[status] + if previous.get("headers", {}) != proposed.get("headers", {}): + errors.append(f"{at} {status}: response headers changed") + old_content, new_content = previous.get("content", {}), proposed.get("content", {}) + if old_content.keys() != new_content.keys(): + errors.append(f"{at} {status}: response media changed") + for media in old_content.keys() & new_content.keys(): + errors.extend(subset(new_content[media].get("schema", {}), old_content[media].get("schema", {}), f"{at} response {status}")) + return sorted(set(errors)) + + +def digest(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def read_bundle(descriptor, component, directory): + required = {"file", "sha256", "version", "source_sha"} + if not isinstance(descriptor, dict) or set(descriptor) != required: + raise ContractError(f"{component}: descriptor requires exactly {sorted(required)}") + version(descriptor["version"]) + if not re.fullmatch(r"[a-f0-9]{40}", descriptor["source_sha"]): + raise ContractError(f"{component}: immutable source SHA required") + relative = Path(descriptor["file"]) + if relative.is_absolute() or ".." in relative.parts: + raise ContractError(f"{component}: bundle file must stay inside the plan directory") + path = directory / relative + if not path.resolve().is_relative_to(directory.resolve()): + raise ContractError(f"{component}: bundle symlink escapes the plan directory") + if not path.is_file(): + raise ContractError(f"{component}: missing historical artifact {path}; never substitute current main") + if not re.fullmatch(r"[a-f0-9]{64}", descriptor["sha256"]) or digest(path) != descriptor["sha256"]: + raise ContractError(f"{component}: bundle SHA-256 mismatch") + bundle = load(path) + if bundle.get("format") != "marreq.contract.v1" or bundle.get("component") != component: + raise ContractError(f"{component}: wrong bundle format/component") + for key in ("version", "source_sha"): + if bundle.get(key) != descriptor[key]: + raise ContractError(f"{component}: bundle {key} disagrees with deployment plan") + resolve(bundle["contract"], bundle["contract"]) + validate_spec(bundle["contract"]) + return bundle + + +def verify_manifest(path): + manifest = load(path) + if not isinstance(manifest, dict) or set(manifest) != {"format", "variant", "backend", "frontend", "mcp"}: + raise ContractError("deployment plan requires format, variant, backend, frontend and mcp") + if manifest["format"] != "marreq.deployment-contract.v1" or manifest["variant"] not in ("server", "cloud"): + raise ContractError("invalid deployment plan format/variant") + bundles = {name: read_bundle(manifest[name], name, Path(path).parent) for name in ("backend", "frontend", "mcp")} + backend = bundles["backend"] + if backend.get("variant") != manifest["variant"]: + raise ContractError("backend artifact deployment variant mismatch") + errors = [] + backend_version = version(backend["version"]) + frontend_range = backend.get("frontend_compatibility", {}) + if set(frontend_range) != {"min_version", "max_version"}: + raise ContractError("backend frontend compatibility metadata is incomplete") + lo, hi = version(frontend_range["min_version"]), version(frontend_range["max_version"]) + if lo > hi: + raise ContractError("backend frontend compatibility range is inverted") + if not lo <= version(bundles["frontend"]["version"]) <= hi: + errors.append("frontend: outside backend's supported consumer range") + for name in ("frontend", "mcp"): + consumer = bundles[name] + low, high = policy(consumer.get("compatibility")) + if backend_version < low or (high is not None and backend_version > high): + errors.append(f"{name}: backend {backend['version']} is outside declared range") + errors.extend(f"{name}: {error}" for error in compare(consumer["contract"], backend["contract"], manifest["variant"])) + return {"compatible": not errors, "variant": manifest["variant"], "versions": {name: bundle["version"] for name, bundle in bundles.items()}, "errors": errors} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--manifest", required=True, type=Path) + parser.add_argument("--candidate-component", choices=["backend", "frontend", "mcp"]) + parser.add_argument("--candidate-version") + parser.add_argument("--candidate-source-sha") + parser.add_argument("--candidate-bundle", type=Path) + args = parser.parse_args() + try: + candidate = (args.candidate_component, args.candidate_version, args.candidate_source_sha) + if any(candidate): + if not all(candidate): + raise ContractError("candidate check requires component, version and source SHA") + manifest = load(args.manifest) + descriptor = manifest[args.candidate_component] + if descriptor["version"] != args.candidate_version or descriptor["source_sha"] != args.candidate_source_sha: + raise ContractError("deployment plan does not reference the exact tagged candidate; refusing to check a different artifact") + if args.candidate_bundle: + if not all(candidate): + raise ContractError("candidate bundle requires candidate identity") + if digest(args.candidate_bundle) != descriptor["sha256"]: + raise ContractError("candidate contract is not the source-exported bundle for this tag") + result = verify_manifest(args.manifest) + except (ValueError, OSError, KeyError, TypeError) as error: + result = {"compatible": False, "errors": [str(error)]} + except Exception as error: # validators also have their own exception types + result = {"compatible": False, "errors": [f"invalid contract: {error}"]} + print(json.dumps(result, indent=2)) + return 0 if result["compatible"] else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/contracts/generate.py b/scripts/contracts/generate.py new file mode 100644 index 00000000..b9037409 --- /dev/null +++ b/scripts/contracts/generate.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Reproduce TypeScript wire types and compiled-Rocket route expectations.""" +import argparse +import json +import sys + +from compatibility import ROOT, load + + +def ts(node): + if node is True or node == {}: + return "unknown" + if node is False: + return "never" + if "$ref" in node: + return node["$ref"].split("/")[-1] + for key in ("oneOf", "anyOf", "allOf"): + if key in node: + join = " & " if key == "allOf" else " | " + return "(" + join.join(ts(item) for item in node[key]) + ")" + if "const" in node: + return json.dumps(node["const"]) + if "enum" in node: + return " | ".join(json.dumps(item) for item in node["enum"]) + kind = node.get("type") + if isinstance(kind, list): + return " | ".join(ts({**node, "type": value}) for value in kind) + if kind == "null": + return "null" + if kind in ("number", "integer"): + return "number" + if kind in ("string", "boolean"): + return kind + if kind == "array": + return f"Array<{ts(node.get('items', {}))}>" + if kind == "object" or "properties" in node: + lines = [] + for name, value in node.get("properties", {}).items(): + optional = "" if name in node.get("required", []) else "?" + lines.append(f" {json.dumps(name)}{optional}: {ts(value)};") + extra = node.get("additionalProperties", True) + if isinstance(extra, dict): + lines.append(f" [key: string]: {ts(extra)};") + elif extra: + lines.append(" [key: string]: unknown;") + return "{\n" + "\n".join(lines) + "\n}" + if node.get("x-marreq-unverified"): + return "unknown" + raise ValueError(f"unsupported TypeScript schema: {node}") + + +def generated(spec): + header = "// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml.\n// Do not edit; this file carries wire types, not a second contract.\n\n" + types = header + "\n\n".join(f"export type {name} = {ts(schema)};" for name, schema in sorted(spec["components"]["schemas"].items())) + "\n" + routes = [] + for path, item in spec["paths"].items(): + for method, operation in item.items(): + routes.append({"method": method.upper(), "path": path, "variants": operation.get("x-marreq-variants", ["server", "cloud"])}) + return { + ROOT / "frontend/src/api/generated-contract.ts": types, + ROOT / "mcp-server/src/generated-contract.ts": types, + ROOT / "docs/developer/api-contract-routes.json": json.dumps(routes, indent=2) + "\n", + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + stale = [] + for path, content in generated(load(ROOT / "docs/developer/openapi.yaml")).items(): + if args.check: + if not path.exists() or path.read_text() != content: + stale.append(str(path.relative_to(ROOT))) + else: + path.write_text(content) + if stale: + print("Generated contract drift; run python3 scripts/contracts/generate.py:\n" + "\n".join(stale), file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/contracts/inventory.py b/scripts/contracts/inventory.py new file mode 100644 index 00000000..9376ed63 --- /dev/null +++ b/scripts/contracts/inventory.py @@ -0,0 +1,26 @@ +"""Source audit helpers, corroborated by Rocket's compiled route inventory. + +This extracts route signatures, not Rust syntax or inferred serialization schemas. +The checked-in OpenAPI remains the authoritative wire contract. +""" +from pathlib import Path +import re + +ROOT = Path(__file__).resolve().parents[2] + + +def source_routes(root=ROOT): + routes = [] + for directory, variant in (("marreq-core/src/api", "shared"), ("marreq-cloud/src/api", "cloud"), ("marreq-server/src/api", "server")): + for file in sorted((root / directory).glob("*.rs")): + text = file.read_text() + # Rocket route attributes always precede a free handler function. + pattern = r'#\[(get|post|put|patch|delete)\(\s*"([^"]+)"(.*?)\)\]\s*pub (?:async )?fn (\w+)\s*\((.*?)\)\s*(?:->\s*(.*?))?\s*\{' + for match in re.finditer(pattern, text, re.S): + method, uri, attributes, name, arguments, returns = match.groups() + base = "" if file.stem == "oauth" else "/api" + path, _, query = uri.partition("?") + path = base + re.sub(r"<([\w]+)(?:\.\.)?>", r"{\1}", path) + guards = re.findall(r"\b(?:ApiUser(?:OrBearer)?|SessionUser|OptionalSessionUser|AdminOnly|Project[A-Z][A-Za-z]*|ProjectsRead|StdioMcpAuditAuth|McpAuditAuth|McpPrincipalAuth|InternalMcpAudit)\b", arguments) + routes.append({"path": path, "method": method, "handler": f"{file.stem}::{name}", "source": str(file.relative_to(root)), "variant": variant, "arguments": arguments.strip(), "returns": (returns or "()").strip(), "query": query, "guards": guards, "data": re.findall(r'data\s*=\s*"<([^>]+)>"', attributes)}) + return routes diff --git a/scripts/contracts/requirements.txt b/scripts/contracts/requirements.txt new file mode 100644 index 00000000..d21b1402 --- /dev/null +++ b/scripts/contracts/requirements.txt @@ -0,0 +1,3 @@ +PyYAML==6.0.3 +jsonschema==4.26.0 +openapi-spec-validator==0.7.2 diff --git a/scripts/contracts/smoke-consumers.mjs b/scripts/contracts/smoke-consumers.mjs new file mode 100644 index 00000000..abbb0821 --- /dev/null +++ b/scripts/contracts/smoke-consumers.mjs @@ -0,0 +1,49 @@ +// Real HTTP producer/consumer smoke test. Run only through test-backend.sh: +// it provides an isolated fixture DB and loopback-only Rocket process. +import assert from 'node:assert/strict'; +import { appendFileSync } from 'node:fs'; +import { MarreqClient } from '../../mcp-server/dist/client.js'; +import { fetchJson } from '../../frontend/src/api/transport.ts'; + +const base = process.env.MARREQ_CONTRACT_BACKEND_URL; +assert.match(base ?? '', /^http:\/\/127\.0\.0\.1:\d+$/); +assert.ok(process.env.MARREQ_CONTRACT_TRANSCRIPT); +const networkFetch = globalThis.fetch; +globalThis.fetch = async (input, init = {}) => { + const url = new URL(input, base); + assert.equal(url.origin, base, 'contract smoke must stay on the isolated backend'); + const response = await networkFetch(url, init); + const text = await response.clone().text(); + let body = null; + try { body = JSON.parse(text); } catch { /* Empty guard and 204 bodies are valid. */ } + // Authorization headers/cookies are never retained in the transcript. + appendFileSync(process.env.MARREQ_CONTRACT_TRANSCRIPT, JSON.stringify({ + method: init.method ?? 'GET', path: url.pathname + url.search, + status: response.status, + request: typeof init.body === 'string' ? JSON.parse(init.body) : null, + response: body, + }) + '\n'); + return response; +}; + +// Execute the actual frontend transport against the running Rocket binary. +const build = await fetchJson('/api/meta/build'); +assert.equal(build.deployment_mode, 'server'); +const capabilities = await fetchJson('/api/meta/deployment'); +assert.equal(capabilities.allows_self_registration, false); + +// These delegated fixture credentials exist only in the ephemeral DB. +const context = { baseUrl: base, apiToken: 'uc-write', projectId: 1, mode: 'draft_write', traceWrite: false }; +const writer = new MarreqClient(context); +const reader = new MarreqClient({ ...context, apiToken: 'uc-read' }); +assert.ok(Array.isArray(await reader.listUseCases())); +const created = await writer.createUseCase({ title: 'Network contract use case', primary_actor: 'Tester' }, 'network-contract-create-key'); +assert.equal(typeof created.id, 'number'); +const document = { schema: 'marreq.use-case.v1', title: 'Network contract use case', main: [{ id: 's_net01', text: 'Exercise the real client against Rocket' }] }; +const saved = await writer.putUseCaseDraft(created.id, created.draft.revision, document); +assert.equal(saved.document.main[0].id, 's_net01'); +const detail = await reader.getUseCase(created.id); +assert.equal(detail.id, created.id); +await assert.rejects(() => reader.createUseCase({ title: 'Denied write' }, 'network-contract-denied-key'), (error) => error.status === 403); +await assert.rejects(() => reader.withProject(2).listUseCases(), (error) => error.status === 403 || /403/.test(error.message)); +console.log('Real frontend transport and MCP HTTP read/write/scope/project-boundary checks passed.'); diff --git a/scripts/contracts/test-backend.sh b/scripts/contracts/test-backend.sh new file mode 100755 index 00000000..4a1c66d4 --- /dev/null +++ b/scripts/contracts/test-backend.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Creates a disposable DB; ignores DATABASE_URL and .env. No production access. +set -euo pipefail +cd "$(dirname "$0")/../.." +work=$(mktemp -d) +container="marreq-contract-$RANDOM-$$" +backend_pid= +cleanup() { if [[ -n "$backend_pid" ]]; then kill "$backend_pid" 2>/dev/null || true; wait "$backend_pid" 2>/dev/null || true; fi; docker rm -f "$container" >/dev/null 2>&1 || true; rm -rf "$work"; } +trap cleanup EXIT +# No bind mounts, persistent volumes or remotely supplied database URLs. +docker run --detach --rm --name "$container" \ + --tmpfs /var/lib/postgresql/data \ + -e POSTGRES_DB=marreq_contract_test -e POSTGRES_USER=contract_test \ + -e POSTGRES_PASSWORD=disposable-contract-test \ + -p 127.0.0.1::5432 pgvector/pgvector:pg17@sha256:ac08538c6f8b9904c33c8224c5e5706dbe760aca29db1d096972b4052c22a75d >/dev/null +ready=false +for attempt in {1..60}; do + if docker exec "$container" pg_isready -U contract_test -d marreq_contract_test >/dev/null 2>&1; then ready=true; break; fi + sleep 1 +done +if [[ "$ready" != true ]]; then echo "isolated contract DB did not become ready" >&2; exit 1; fi +for migration in marreq-core/migrations/*/up.sql; do + docker exec -i "$container" psql -v ON_ERROR_STOP=1 -U contract_test -d marreq_contract_test < "$migration" > "$work/migration.log" 2>&1 || { cat "$work/migration.log"; exit 1; } +done +port=$(docker port "$container" 5432/tcp) +port=${port##*:} +export MARREQ_TEST_DATABASE_URL="postgres://contract_test:disposable-contract-test@127.0.0.1:$port/marreq_contract_test" +export DATABASE_URL="$MARREQ_TEST_DATABASE_URL" +export MARREQ_CONTRACT_TRANSCRIPT="$work/exchanges.jsonl" +cargo test --locked -p marreq-core --no-default-features --test delegated_oauth_mcp_tools_postgres_integration --no-run --message-format=json > "$work/test-build.jsonl" +test_executable=$(python3 -c 'import json,sys; print(next(r["executable"] for r in map(json.loads,open(sys.argv[1])) if r.get("executable") and r["target"]["name"]=="delegated_oauth_mcp_tools_postgres_integration"))' "$work/test-build.jsonl") +# A clean working directory and environment prevent dotenv/SMTP/semantic-search +# configuration inherited from a developer machine from entering test processes. +(cd "$work" && env -i PATH="$PATH" LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ + MARREQ_TEST_DATABASE_URL="$MARREQ_TEST_DATABASE_URL" \ + MARREQ_CONTRACT_TRANSCRIPT="$MARREQ_CONTRACT_TRANSCRIPT" \ + MARREQ_UI_MODE=api_only "$test_executable" --nocapture) +# Run the actual frontend transport and MCP HTTP client against a live socket. +# All database and service configuration is pinned to this ephemeral test stack. +npm run build --prefix mcp-server +cargo build --locked -p marreq-server --message-format=json > "$work/server-build.jsonl" +server_executable=$(python3 -c 'import json,sys; print(next(r["executable"] for r in map(json.loads,open(sys.argv[1])) if r.get("executable") and r["target"]["name"]=="marreq-server"))' "$work/server-build.jsonl") +backend_port=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()') +export ROCKET_ADDRESS=127.0.0.1 ROCKET_PORT="$backend_port" +export ROCKET_DATABASES="{my_db={url=\"$DATABASE_URL\",pool_size=2}}" +export MARREQ_PUBLIC_BASE_URL=http://localhost:8080 MARREQ_MCP_PUBLIC_URL=http://localhost:8080/mcp +export MARREQ_MCP_AUDIT_SECRET=integration-test-audit-secret-32-bytes +export MARREQ_AUTO_SEED=false MARREQ_UI_MODE=api_only +export MARREQ_CONTRACT_BACKEND_URL="http://127.0.0.1:$backend_port" +(cd "$work" && exec env -i PATH="$PATH" LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ + DATABASE_URL="$DATABASE_URL" ROCKET_ADDRESS=127.0.0.1 ROCKET_PORT="$backend_port" \ + ROCKET_DATABASES="$ROCKET_DATABASES" MARREQ_PUBLIC_BASE_URL="$MARREQ_PUBLIC_BASE_URL" \ + MARREQ_MCP_PUBLIC_URL="$MARREQ_MCP_PUBLIC_URL" MARREQ_MCP_AUDIT_SECRET="$MARREQ_MCP_AUDIT_SECRET" \ + MARREQ_AUTO_SEED=false MARREQ_UI_MODE=api_only "$server_executable") > "$work/backend.log" 2>&1 & +backend_pid=$! +ready=false +for attempt in {1..60}; do + if ! kill -0 "$backend_pid" 2>/dev/null; then cat "$work/backend.log"; exit 1; fi + if curl --silent --fail "$MARREQ_CONTRACT_BACKEND_URL/api/meta/health" > /dev/null; then ready=true; break; fi + sleep 1 +done +if [[ "$ready" != true ]]; then cat "$work/backend.log"; exit 1; fi +frontend/node_modules/.bin/rolldown scripts/contracts/smoke-consumers.mjs --platform node --format esm --file "$work/smoke.mjs" +node "$work/smoke.mjs" +# Fails if the test skipped, produced no transcript, or omitted a critical case. +python3 scripts/contracts/verify_transcript.py "$MARREQ_CONTRACT_TRANSCRIPT" diff --git a/scripts/contracts/test_compatibility.py b/scripts/contracts/test_compatibility.py new file mode 100644 index 00000000..823c9d34 --- /dev/null +++ b/scripts/contracts/test_compatibility.py @@ -0,0 +1,227 @@ +import copy +import hashlib +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + +from compatibility import ContractError, ROOT, compare, policy, subset, verify_manifest + + +def spec(): + obj = {"type": "object", "properties": {"id": {"type": "integer"}, "state": {"type": "string", "enum": ["draft", "approved"]}}, "required": ["id", "state"], "additionalProperties": True} + return {"openapi": "3.1.0", "info": {"title": "fixture", "version": "1.0.0"}, "paths": {"/api/things/{id}": {"post": {"security": [{"bearerAuth": []}], "parameters": [{"in": "path", "name": "id", "required": True, "schema": {"type": "integer"}}], "requestBody": {"required": True, "content": {"application/json": {"schema": {"type": "object", "required": ["title"], "properties": {"title": {"type": "string"}}, "additionalProperties": False}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": obj}}}, "401": {"description": "Unauthorized"}}}}}, "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer"}}}} + + +def operation(document): + return document["paths"]["/api/things/{id}"]["post"] + + +def response(document): + return operation(document)["responses"]["200"]["content"]["application/json"]["schema"] + + +def request(document): + return operation(document)["requestBody"]["content"]["application/json"]["schema"] + + +class CompatibilityTests(unittest.TestCase): + def setUp(self): + self.old = spec() + self.new = copy.deepcopy(self.old) + + def check(self): + return compare(self.old, self.new, "server") + + def test_valid_openapi(self): + self.assertEqual(self.check(), []) + + def test_removed_route(self): + self.new["paths"] = {} + self.assertIn("endpoint missing", self.check()[0]) + + def test_incompatible_request_type(self): + request(self.new)["properties"]["title"] = {"type": "integer"} + self.assertTrue(self.check()) + + def test_incompatible_response_type(self): + response(self.new)["properties"]["id"] = {"type": "string"} + self.assertTrue(self.check()) + + def test_removed_required_response(self): + response(self.new)["required"].remove("id") + self.assertTrue(self.check()) + + def test_new_required_request(self): + request(self.new)["properties"]["reason"] = {"type": "string"} + request(self.new)["required"].append("reason") + self.assertTrue(self.check()) + + def test_optional_request_addition(self): + request(self.new)["properties"]["reason"] = {"type": "string"} + self.assertEqual(self.check(), []) + + def test_response_addition(self): + response(self.new)["properties"]["extra"] = {"type": "boolean"} + response(self.new)["required"].append("extra") + self.assertEqual(self.check(), []) + + def test_response_enum_extension_breaks_exhaustive_consumers(self): + response(self.new)["properties"]["state"]["enum"].append("reviewed") + self.assertTrue(self.check()) + + def test_request_enum_restriction(self): + request(self.old)["properties"]["title"]["enum"] = ["a", "b"] + request(self.new)["properties"]["title"]["enum"] = ["a"] + self.assertTrue(self.check()) + + def test_oauth_scope_mismatch(self): + operation(self.new)["x-marreq-bearer-scopes"] = ["things:write"] + self.assertTrue(self.check()) + + def test_authentication_mismatch(self): + operation(self.new)["security"] = [] + self.assertTrue(self.check()) + + def test_status_code_mismatch(self): + operation(self.new)["responses"]["201"] = operation(self.new)["responses"].pop("200") + self.assertTrue(self.check()) + + def test_variants(self): + operation(self.old)["x-marreq-variants"] = ["cloud"] + self.new["paths"] = {} + self.assertEqual(self.check(), []) + self.assertTrue(compare(self.old, self.new, "cloud")) + + def test_unverified_contract_fails_even_when_identical(self): + operation(self.old)["x-marreq-unverified"] = True + self.assertTrue(compare(self.old, self.old, "cloud")) + + def test_unsupported_constraint_changes_fail_closed(self): + self.assertTrue(subset({"type": "string"}, {"type": "string", "pattern": "a+"}, "request")) + + def test_nullable_response_breaks_nonnullable_consumer(self): + self.assertTrue(subset({"type": ["string", "null"]}, {"type": "string"}, "response")) + + def test_metadata_invalid_or_incomplete(self): + for value in ({}, None, {"requires_backend_min": "1.x"}, {"requires_backend_min": "2.0.0", "requires_backend_max": "1.0.0"}, {"requires_backend_min": "1.0.0", "typo": "x"}): + with self.subTest(value=value), self.assertRaises(ContractError): + policy(value) + + def deployment(self, directory, incompatible=False, backend_version="1.1.0"): + plan = {"format": "marreq.deployment-contract.v1", "variant": "server"} + for component, ver in (("backend", backend_version), ("frontend", "1.0.0"), ("mcp", "1.2.0")): + contract = spec() + if incompatible and component == "backend": + response(contract)["required"].remove("id") + bundle = {"format": "marreq.contract.v1", "component": component, "version": ver, "source_sha": "a" * 40, "contract": contract} + if component == "backend": + bundle.update(variant="server", frontend_compatibility={"min_version": "1.0.0", "max_version": "2.0.0"}) + else: + bundle["compatibility"] = {"requires_backend_min": "1.0.0", "requires_backend_max": "1.9.0"} + data = json.dumps(bundle).encode() + (directory / f"{component}.json").write_bytes(data) + plan[component] = {"file": f"{component}.json", "sha256": hashlib.sha256(data).hexdigest(), "version": ver, "source_sha": "a" * 40} + path = directory / "plan.json" + path.write_text(json.dumps(plan)) + return path + + def test_unchanged_frontend_and_mcp_with_new_compatible_backend(self): + with tempfile.TemporaryDirectory() as temp: + result = verify_manifest(self.deployment(Path(temp))) + self.assertTrue(result["compatible"]) + self.assertNotEqual(result["versions"]["frontend"], result["versions"]["backend"]) + self.assertNotEqual(result["versions"]["mcp"], result["versions"]["backend"]) + + def test_unchanged_consumers_with_incompatible_backend(self): + with tempfile.TemporaryDirectory() as temp: + result = verify_manifest(self.deployment(Path(temp), incompatible=True)) + self.assertFalse(result["compatible"]) + self.assertTrue(any(e.startswith("frontend:") for e in result["errors"])) + self.assertTrue(any(e.startswith("mcp:") for e in result["errors"])) + + def test_versions_fail_independently_of_contracts(self): + with tempfile.TemporaryDirectory() as temp: + result = verify_manifest(self.deployment(Path(temp), backend_version="2.0.0")) + self.assertFalse(result["compatible"]) + + def test_missing_historical_artifact(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + (Path(temp) / "mcp.json").unlink() + with self.assertRaisesRegex(ContractError, "missing historical artifact"): + verify_manifest(path) + + def test_tampered_artifact(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + with (Path(temp) / "frontend.json").open("a") as stream: + stream.write(" ") + with self.assertRaisesRegex(ContractError, "SHA-256 mismatch"): + verify_manifest(path) + + def test_descriptor_version_cannot_substitute_another_artifact(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + plan = json.loads(path.read_text()) + plan["frontend"]["version"] = "1.0.1" + path.write_text(json.dumps(plan)) + with self.assertRaisesRegex(ContractError, "version disagrees"): + verify_manifest(path) + + def test_bundle_path_cannot_escape_plan_directory(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + plan = json.loads(path.read_text()) + plan["mcp"]["file"] = "../mcp.json" + path.write_text(json.dumps(plan)) + with self.assertRaisesRegex(ContractError, "inside the plan directory"): + verify_manifest(path) + + def test_backend_variant_cannot_be_substituted(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + plan = json.loads(path.read_text()) + plan["variant"] = "cloud" + path.write_text(json.dumps(plan)) + with self.assertRaisesRegex(ContractError, "variant mismatch"): + verify_manifest(path) + + def test_candidate_plan_must_reference_tagged_source(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + import sys + result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path), "--candidate-component", "backend", "--candidate-version", "1.1.0", "--candidate-source-sha", "b" * 40], capture_output=True, text=True) + self.assertEqual(result.returncode, 1) + self.assertIn("exact tagged candidate", json.loads(result.stdout)["errors"][0]) + + def test_invalid_openapi_does_not_pass_compatibility(self): + self.new.pop("info") + with self.assertRaises(Exception): + self.check() + + def test_external_refs_are_rejected_before_any_validator_network_access(self): + response(self.new)["properties"]["id"] = {"$ref": "https://example.test/schema.json"} + with self.assertRaisesRegex(ContractError, "external or recursive"): + self.check() + + def test_release_gate_zero_for_compatible_independent_artifacts(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp)) + import sys + result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path)], capture_output=True, text=True) + self.assertEqual(result.returncode, 0) + self.assertTrue(json.loads(result.stdout)["compatible"]) + + def test_release_gate_nonzero_on_real_violation(self): + with tempfile.TemporaryDirectory() as temp: + path = self.deployment(Path(temp), incompatible=True) + import sys + result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path)], capture_output=True, text=True) + self.assertEqual(result.returncode, 1) + self.assertFalse(json.loads(result.stdout)["compatible"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/contracts/test_contract_tooling.py b/scripts/contracts/test_contract_tooling.py new file mode 100644 index 00000000..78ef3d3f --- /dev/null +++ b/scripts/contracts/test_contract_tooling.py @@ -0,0 +1,70 @@ +import copy +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + +from compatibility import ContractError, ROOT, load +from check import check +from check_baseline import check as check_baseline +from generate import generated +from test_compatibility import spec, response +from verify_transcript import verify + + +class ProducerTests(unittest.TestCase): + def test_openapi_has_all_source_routes_and_generated_artifacts(self): + # Review lock is checked separately and cannot stand in for route/schema checks. + errors, gaps = check() + self.assertEqual(errors, []) + self.assertTrue(gaps) # Be honest about the bootstrap's incomplete evidence. + + def test_documented_route_missing_from_actual_source_is_rejected(self): + from inventory import source_routes + routes = source_routes() + with patch("check.source_routes", return_value=routes[1:]): + errors, _ = check() + self.assertTrue(any("documented route missing in backend" in error for error in errors)) + + def test_cloud_route_accidentally_enabled_on_server_is_rejected(self): + from inventory import source_routes + routes = copy.deepcopy(source_routes()) + next(route for route in routes if route["variant"] == "cloud")["variant"] = "shared" + with patch("check.source_routes", return_value=routes): + errors, _ = check() + self.assertTrue(any("deployment variant mismatch" in error for error in errors)) + + def test_source_change_without_contract_review_is_rejected(self): + with patch("check.source_evidence", return_value={"changed.rs": "f" * 64}): + errors, _ = check() + self.assertTrue(any("API source changed" in error for error in errors)) + + def test_release_completeness_cannot_pass_inventory_only_entries(self): + errors, gaps = check(require_complete=True) + self.assertTrue(gaps) + self.assertTrue(any("unverified wire contract" in error for error in errors)) + + def test_audited_baseline_rejects_required_response_removal(self): + old, new = spec(), spec() + old["x-marreq-contract-complete"] = False + response(new)["required"].remove("id") + self.assertTrue(check_baseline(old, new)["errors"]) + + def test_generated_types_come_from_the_contract(self): + document = load(ROOT / "docs/developer/openapi.yaml") + result = generated(document) + document["components"]["schemas"]["BuildInfo"]["properties"]["new_required"] = {"type": "integer"} + document["components"]["schemas"]["BuildInfo"]["required"].append("new_required") + self.assertNotEqual(result, generated(document)) + + def test_empty_integration_transcript_is_not_a_passing_backend_test(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "exchanges.jsonl" + path.write_text("") + with self.assertRaisesRegex(ContractError, "lacks critical cases"): + verify(path) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/contracts/verify_transcript.py b/scripts/contracts/verify_transcript.py new file mode 100644 index 00000000..c513cb5b --- /dev/null +++ b/scripts/contracts/verify_transcript.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Validate real Rocket responses and requests recorded by the DB integration test. + +No credentials are recorded. Inventory-only operations are counted separately. +""" +import argparse +import copy +import tempfile +from pathlib import Path +import json +import re +import sys + +from jsonschema import Draft202012Validator, ValidationError +from compatibility import ROOT, ContractError, load, operations, resolve + + +def verify(path): + spec = load(ROOT / "docs/developer/openapi.yaml") + declared = operations(resolve(spec, spec), "server") + records = [json.loads(line) for line in path.read_text().splitlines() if line.strip()] + checked, inventory_only, seen = 0, 0, set() + for record in records: + matches = [(p, op) for (p, m), op in declared.items() if m.upper() == record["method"] and re.fullmatch(re.sub(r"\{[^}]+\}", "[^/]+", re.escape(p).replace(r"\{", "{").replace(r"\}", "}")), record["path"].split("?")[0])] + if matches: + specificity = min(p.count("{") for p, _ in matches) + matches = [(p, op) for p, op in matches if p.count("{") == specificity] + if len(matches) != 1: + raise ContractError(f"ambiguous or missing operation for {record['method']} {record['path']}") + path_template, op = matches[0] + if op.get("x-marreq-unverified"): + inventory_only += 1 + continue + status = str(record["status"]) + response = op["responses"].get(status, op["responses"].get("default")) + if response is None: + raise ContractError(f"{path_template}: undocumented actual status {status}") + body = record["response"] + schema = response.get("content", {}).get("application/json", {}).get("schema") + if schema is not None: + Draft202012Validator(schema).validate(body) + if record.get("request") is not None and status.startswith("2"): + request_schema = op.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema") + if request_schema is None: + raise ContractError(f"{path_template}: undocumented JSON request") + Draft202012Validator(request_schema).validate(record["request"]) + checked += 1 + seen.add((record["method"], path_template, status)) + required = {("GET", "/api/meta/build", "200"), ("GET", "/api/meta/deployment", "200"), ("GET", "/api/projects/{project_id}/use-cases", "200"), ("POST", "/api/projects/{project_id}/use-cases", "201"), ("PUT", "/api/projects/{project_id}/use-cases/{id}/draft", "200"), ("GET", "/api/projects/{project_id}/use-cases", "403")} + missing = required - seen + if missing: + raise ContractError(f"integration transcript lacks critical cases: {sorted(missing)}") + return {"validated_exchanges": checked, "inventory_only_exchanges": inventory_only} + + +def negative_controls(path): + """Demonstrate rejection using corrupted copies of actual backend traffic.""" + records = [json.loads(line) for line in path.read_text().splitlines() if line.strip()] + cases = [] + for label in ("removed guaranteed response property", "incompatible response type", "incompatible MCP request"): + mutated = copy.deepcopy(records) + if label == "incompatible MCP request": + entry = next(r for r in mutated if r["method"] == "POST" and r["status"] == 201 and r["path"].endswith("/use-cases")) + entry["request"]["title"] = 123 + else: + entry = next(r for r in mutated if r["path"] == "/api/meta/build") + if label == "removed guaranteed response property": + del entry["response"]["backend_version"] + else: + entry["response"]["backend_version"] = 123 + with tempfile.TemporaryDirectory() as directory: + candidate = Path(directory) / "negative.jsonl" + candidate.write_text("\n".join(json.dumps(r) for r in mutated) + "\n") + try: + verify(candidate) + except ValidationError: + cases.append(label) + else: + raise ContractError(f"negative control was silently accepted: {label}") + return cases + + +if __name__ == "__main__": + from pathlib import Path + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("transcript", type=Path) + args = parser.parse_args() + result = verify(args.transcript) + result["rejected_negative_controls"] = negative_controls(args.transcript) + print(json.dumps(result, indent=2)) From 839b92e0e77782c3211a361be026131baf0f98b7 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 27 Oct 2026 20:21:46 +0100 Subject: [PATCH 02/18] wip --- .github/workflows/api-contracts.yml | 56 - .github/workflows/marreq-ci.yml | 29 +- .github/workflows/release-artifacts.yml | 23 +- .gitignore | 4 - Cargo.lock | 493 ++- Cargo.toml | 2 +- docker/Dockerfile | 2 - docker/frontend/Dockerfile | 2 - docs/developer/api-contract-routes.json | 1958 ----------- .../developer/api-contract-source-review.json | 378 -- docs/developer/http-api-contract.md | 274 +- docs/developer/mcp-setup.md | 21 +- docs/developer/openapi.yaml | 3033 +++-------------- docs/developer/use-cases-api.md | 19 +- docs/releases.md | 167 +- frontend/src/api/__tests__/catalog.test.ts | 4 +- frontend/src/api/catalog.ts | 4 +- frontend/src/api/generated-contract.ts | 934 ++--- frontend/src/api/types.ts | 21 +- frontend/src/pages/SystemLogsPage.tsx | 4 +- .../__tests__/ReportBuilderPage.test.tsx | 1 + marreq-cloud/Cargo.toml | 2 +- marreq-cloud/tests/api_contract_inventory.rs | 73 +- ...ed_oauth_mcp_tools_postgres_integration.rs | 7 + mcp-server/Dockerfile | 1 - mcp-server/dist/client.js | 104 +- mcp-server/dist/gap-tools.js | 276 ++ mcp-server/dist/generated-contract.js | 1 + mcp-server/dist/index.js | 98 +- mcp-server/dist/pagination.js | 49 + mcp-server/dist/prompts.js | 52 + mcp-server/dist/resources.js | 49 + mcp-server/package-lock.json | 66 +- mcp-server/src/client.ts | 13 +- mcp-server/src/generated-contract.ts | 888 +++-- mcp-server/src/remote.ts | 4 +- package-lock.json | 258 +- package.json | 1 + scripts/check-api-compatibility.sh | 2 +- .../__pycache__/check.cpython-314.pyc | Bin 0 -> 8966 bytes .../check_baseline.cpython-314.pyc | Bin 0 -> 2385 bytes .../__pycache__/compatibility.cpython-314.pyc | Bin 0 -> 23840 bytes .../__pycache__/generate.cpython-314.pyc | Bin 0 -> 6439 bytes .../__pycache__/inventory.cpython-314.pyc | Bin 0 -> 2736 bytes .../test_compatibility.cpython-314.pyc | Bin 0 -> 24708 bytes .../test_contract_tooling.cpython-314.pyc | Bin 0 -> 7690 bytes .../verify_transcript.cpython-314.pyc | Bin 0 -> 8050 bytes scripts/contracts/bundle.py | 59 - scripts/contracts/check.py | 80 - scripts/contracts/check_baseline.py | 26 - scripts/contracts/compatibility.py | 294 -- scripts/contracts/generate.py | 84 - scripts/contracts/inventory.py | 26 - scripts/contracts/requirements.txt | 3 - scripts/contracts/test_compatibility.py | 227 -- scripts/contracts/test_contract_tooling.py | 70 - scripts/contracts/verify_transcript.py | 90 - scripts/generate-api-types.mjs | 26 + scripts/install-oasdiff.sh | 15 + ...-consumers.mjs => smoke-api-consumers.mjs} | 43 +- .../test-backend.sh => test-api-contracts.sh} | 52 +- tools/marreq-contracts/Cargo.toml | 20 + tools/marreq-contracts/src/artifact.rs | 326 ++ tools/marreq-contracts/src/contract.rs | 387 +++ tools/marreq-contracts/src/coverage.rs | 714 ++++ tools/marreq-contracts/src/lib.rs | 4 + tools/marreq-contracts/src/main.rs | 204 ++ tools/marreq-contracts/src/traffic.rs | 103 + tools/marreq-contracts/tests/compatibility.rs | 459 +++ 69 files changed, 5300 insertions(+), 7385 deletions(-) delete mode 100644 .github/workflows/api-contracts.yml delete mode 100644 docs/developer/api-contract-routes.json delete mode 100644 docs/developer/api-contract-source-review.json create mode 100644 mcp-server/dist/gap-tools.js create mode 100644 mcp-server/dist/generated-contract.js create mode 100644 mcp-server/dist/pagination.js create mode 100644 mcp-server/dist/prompts.js create mode 100644 mcp-server/dist/resources.js create mode 100644 scripts/contracts/__pycache__/check.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/check_baseline.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/compatibility.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/generate.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/inventory.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/test_compatibility.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc create mode 100644 scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc delete mode 100644 scripts/contracts/bundle.py delete mode 100644 scripts/contracts/check.py delete mode 100644 scripts/contracts/check_baseline.py delete mode 100644 scripts/contracts/compatibility.py delete mode 100644 scripts/contracts/generate.py delete mode 100644 scripts/contracts/inventory.py delete mode 100644 scripts/contracts/requirements.txt delete mode 100644 scripts/contracts/test_compatibility.py delete mode 100644 scripts/contracts/test_contract_tooling.py delete mode 100644 scripts/contracts/verify_transcript.py create mode 100644 scripts/generate-api-types.mjs create mode 100755 scripts/install-oasdiff.sh rename scripts/{contracts/smoke-consumers.mjs => smoke-api-consumers.mjs} (51%) rename scripts/{contracts/test-backend.sh => test-api-contracts.sh} (54%) create mode 100644 tools/marreq-contracts/Cargo.toml create mode 100644 tools/marreq-contracts/src/artifact.rs create mode 100644 tools/marreq-contracts/src/contract.rs create mode 100644 tools/marreq-contracts/src/coverage.rs create mode 100644 tools/marreq-contracts/src/lib.rs create mode 100644 tools/marreq-contracts/src/main.rs create mode 100644 tools/marreq-contracts/src/traffic.rs create mode 100644 tools/marreq-contracts/tests/compatibility.rs diff --git a/.github/workflows/api-contracts.yml b/.github/workflows/api-contracts.yml deleted file mode 100644 index da7ff175..00000000 --- a/.github/workflows/api-contracts.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: API contracts - -on: - workflow_call: - -permissions: - contents: read - -jobs: - contracts: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - uses: actions/setup-python@v6 - with: - python-version: '3.12' - cache: pip - cache-dependency-path: scripts/contracts/requirements.txt - - name: Install contract validation tooling - run: python -m pip install -r scripts/contracts/requirements.txt - - name: Validate OpenAPI, source review, metadata and generated consumers - run: python scripts/contracts/check.py - - name: Exercise compatible and breaking historical combinations - run: python -m unittest discover -s scripts/contracts -v - - name: Check audited contracts against PR base - if: github.event_name == 'pull_request' - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - run: | - git show "$BASE_SHA:docs/developer/openapi.yaml" > /tmp/base-openapi.yaml - python scripts/contracts/check_baseline.py /tmp/base-openapi.yaml - - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 - with: - toolchain: stable - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - shared-key: api-contracts - - name: Install PostgreSQL link dependency - run: sudo apt-get update && sudo apt-get install -y libpq-dev - - name: Verify actual compiled Server and Cloud route mounts - run: cargo test --locked -p marreq-cloud --test api_contract_inventory - - uses: actions/setup-node@v7 - with: - node-version: '22' - - name: Typecheck and test consumers of generated wire types - run: | - npm ci --prefix frontend - npm ci --prefix mcp-server - npm run build --prefix frontend - npm run build --prefix mcp-server - npm test --prefix frontend - npm test --prefix mcp-server - - name: Verify real Rocket and HTTP consumers with isolated migrated PostgreSQL - run: scripts/contracts/test-backend.sh diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index b0b478f7..00e27265 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -15,7 +15,34 @@ concurrency: jobs: api-contracts: - uses: ./.github/workflows/api-contracts.yml + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 + with: + toolchain: stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: npm + - name: Install pinned standards tooling + run: scripts/install-oasdiff.sh + - name: Test native compatibility semantics and CLI failures + run: cargo test --locked -p marreq-contracts + - name: Install TypeScript tooling and consumers + run: | + npm ci + npm ci --prefix frontend + npm ci --prefix mcp-server + - name: Check generated consumer schemas + run: node scripts/generate-api-types.mjs --check + - name: Verify compiled Server/Cloud operations + run: cargo test --locked -p marreq-cloud --test api_contract_inventory + - name: Exercise isolated real Server/Cloud and HTTP consumers + run: scripts/test-api-contracts.sh + - name: Require verified contracts for every current consumer operation + run: cargo run --locked -p marreq-contracts -- validate lint: runs-on: ubuntu-latest diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index cefd10bc..c9bff04c 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -12,20 +12,21 @@ permissions: packages: write jobs: - contract-tests: - uses: ./.github/workflows/api-contracts.yml - compatibility: - needs: contract-tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # v1 with: - python-version: '3.12' - - run: python -m pip install -r scripts/contracts/requirements.txt - - name: Require complete producer contracts before publication - run: python scripts/contracts/check.py --require-complete + toolchain: stable + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - name: Install pinned OpenAPI standards engine + run: scripts/install-oasdiff.sh + - name: Test contract semantics and validate producer document + run: | + cargo test --locked -p marreq-contracts + cargo run --locked -p marreq-contracts -- validate --document-only + cargo test --locked -p marreq-cloud --test api_contract_inventory - name: Require immutable deployment-plan revision id: plan env: @@ -52,9 +53,9 @@ jobs: *) echo "unsupported component tag" >&2; exit 1 ;; esac if [[ "$component" == backend ]]; then - python scripts/contracts/bundle.py --component backend --variant server --version "$version" --output /tmp/candidate-contract.json + cargo run --locked -p marreq-contracts -- bundle --component backend --variant server --version "$version" --output /tmp/candidate-contract.json else - python scripts/contracts/bundle.py --component "$component" --version "$version" --output /tmp/candidate-contract.json + cargo run --locked -p marreq-contracts -- bundle --component "$component" --version "$version" --output /tmp/candidate-contract.json fi scripts/check-api-compatibility.sh \ --manifest .release-contracts/deployment.json \ diff --git a/.gitignore b/.gitignore index 479399d5..7c21b673 100644 --- a/.gitignore +++ b/.gitignore @@ -79,7 +79,3 @@ _bmad-output/ .cursorignore AGENTS.md - -# Python contract tooling -__pycache__/ -*.pyc diff --git a/Cargo.lock b/Cargo.lock index 9130bfbb..b6141e14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.5" @@ -84,6 +98,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "allsorts-azul" version = "0.17.2" @@ -116,6 +136,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -356,7 +426,7 @@ checksum = "591eba69b7c085632e22ca03606f0bbea68d53bbe26c8962e2d549af20c6a561" dependencies = [ "paste", "roman-numerals-rs", - "strum", + "strum 0.27.2", "unic-langid", "unicode-normalization", "unscanny", @@ -443,6 +513,12 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "brotli-decompressor" version = "5.0.3" @@ -484,6 +560,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.25.2" @@ -673,6 +755,46 @@ dependencies = [ "serde_path_to_error", ] +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + [[package]] name = "cmake" version = "0.1.58" @@ -730,6 +852,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.8" @@ -962,9 +1090,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "fe3e9d21d39e6f39ff46e03d5b621424f88ee23d8a1672c6bfc9ccc7f2da4f6a" dependencies = [ "cmov", ] @@ -1065,6 +1193,12 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "data-url" version = "0.3.2" @@ -1371,6 +1505,9 @@ name = "email_address" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" @@ -1439,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1462,6 +1599,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fancy-regex" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fast-float2" version = "0.2.4" @@ -1555,6 +1703,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fluent-uri" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc74ac4d8359ae70623506d512209619e5cf8f347124910440dbc221714b328e" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1623,6 +1782,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" +dependencies = [ + "num", + "num-bigint", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1736,6 +1905,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -1745,7 +1928,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasm-bindgen", ] @@ -1879,6 +2062,8 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -2619,9 +2804,15 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -2761,6 +2952,60 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonschema" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42a11b47492e6a2cc76e9c7bcc8077776cb31dac444e0795d7f61bcc6441c08" +dependencies = [ + "ahash", + "bytecount", + "data-encoding", + "email_address", + "fancy-regex 0.19.2", + "fraction", + "getrandom 0.3.4", + "itoa", + "jsonschema-regex", + "jsonschema-value", + "num-cmp", + "num-traits", + "percent-encoding", + "referencing", + "regex", + "serde", + "serde_json", + "strum 0.28.0", + "unicode-general-category", + "uuid-simd", +] + +[[package]] +name = "jsonschema-regex" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107aeb6ff14b8deb5610096a8ce5c554ec89806b521147eaeb3cae05b07edb7a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "jsonschema-value" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e281d6b177bb4834f2ffd6f92aa0443cffe29c996b251c8d94eb6f456186feba" +dependencies = [ + "ahash", + "bytecount", + "fraction", + "getrandom 0.3.4", + "memchr", + "num-cmp", + "num-traits", + "serde_json", + "zmij", +] + [[package]] name = "kamadak-exif" version = "0.6.1" @@ -2992,9 +3237,9 @@ dependencies = [ "chrono", "diesel", "lettre", + "marreq-contracts", "marreq-core", "rand 0.10.3", - "regex", "rocket", "serde", "serde_json", @@ -3004,6 +3249,24 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "marreq-contracts" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "jsonschema", + "semver", + "serde", + "serde_json", + "serde_yaml_ng", + "sha2 0.11.1", + "tempfile", + "tree-sitter", + "tree-sitter-typescript", + "walkdir", +] + [[package]] name = "marreq-core" version = "0.1.0" @@ -3092,6 +3355,12 @@ dependencies = [ "libc", ] +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + [[package]] name = "mime" version = "0.3.17" @@ -3227,6 +3496,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.8" @@ -3253,6 +3536,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -3278,6 +3576,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3333,6 +3642,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -3446,6 +3761,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "p256" version = "0.13.2" @@ -3970,7 +4291,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.5", "thiserror 2.0.21", "tokio", "tracing", @@ -4009,9 +4330,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.5", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4029,6 +4350,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972" +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -4167,6 +4494,23 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "referencing" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42818036d3b37cda6bd4c494543158a36e431cb717015ef7c93acc6ba214d3b" +dependencies = [ + "ahash", + "fluent-uri", + "getrandom 0.3.4", + "hashbrown 0.17.1", + "itoa", + "micromap", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" version = "1.13.1" @@ -4536,7 +4880,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4594,7 +4938,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4750,6 +5094,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -4797,6 +5145,8 @@ version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ + "foldhash", + "indexmap 2.14.2", "itoa", "memchr", "serde", @@ -4891,6 +5241,19 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap 2.14.2", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5107,6 +5470,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + [[package]] name = "strict-num" version = "0.1.1" @@ -5139,7 +5508,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -5154,6 +5532,18 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subsetter" version = "0.2.6" @@ -5231,7 +5621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925" dependencies = [ "bincode", - "fancy-regex", + "fancy-regex 0.16.2", "flate2", "fnv", "once_cell", @@ -5299,7 +5689,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5432,9 +5822,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.54.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -5649,6 +6039,36 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree-sitter" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0af592be68c579aa78a16846bd19422978c3c52e438523d45ff5d1bff1f9d4a" + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6247,6 +6667,22 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "valuable" version = "0.1.1" @@ -6312,6 +6748,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.5.0" @@ -6337,6 +6779,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.129" @@ -6511,7 +6962,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6747,6 +7198,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "write-fonts" version = "0.48.1" diff --git a/Cargo.toml b/Cargo.toml index ee7c0dd6..82233590 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ # Virtual workspace: three-crate split โ€” marreq-core (lib), marreq-server (bin), marreq-cloud (bin). [workspace] resolver = "2" -members = ["marreq-core", "marreq-server", "marreq-cloud"] +members = ["marreq-core", "marreq-server", "marreq-cloud", "tools/marreq-contracts"] [workspace.package] edition = "2024" diff --git a/docker/Dockerfile b/docker/Dockerfile index 303f7ff3..0de03a8e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -85,8 +85,6 @@ COPY ${MARREQ_BIN}/Rocket.toml ./Rocket.toml # diesel CLI uses /app/diesel.toml + /app/migrations (see docker-entrypoint.sh) COPY marreq-core/diesel.toml ./diesel.toml -COPY docs/developer/openapi.yaml /app/contracts/openapi.yaml -COPY marreq-core/frontend_compatibility.json /app/contracts/frontend_compatibility.json # Attachment storage (MARREQ_ATTACHMENTS_DIR). Created here so a new named # volume mounted on it inherits the marreq owner. diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index a644685a..868d4453 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -28,8 +28,6 @@ ENV MARREQ_UMAMI_WEBSITE_ID= ENV MARREQ_UMAMI_HOST=umami ENV MARREQ_UMAMI_PORT=3000 COPY --from=build /build/frontend/dist /usr/share/nginx/html -COPY frontend/compatibility.json /usr/share/nginx/html/contracts/compatibility.json -COPY docs/developer/openapi.yaml /usr/share/nginx/html/contracts/openapi.yaml COPY docker/frontend/nginx.conf.template /etc/nginx/templates/default.conf.template COPY --chmod=755 docker/frontend/40-marreq-umami.sh /docker-entrypoint.d/40-marreq-umami.sh EXPOSE 80 diff --git a/docs/developer/api-contract-routes.json b/docs/developer/api-contract-routes.json deleted file mode 100644 index fdfce3d3..00000000 --- a/docs/developer/api-contract-routes.json +++ /dev/null @@ -1,1958 +0,0 @@ -[ - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}/activity", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{id}/activity", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/applicability", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/applicability", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/applicability/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/applicability/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/applicability/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/attachments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/attachments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/attachments/{attachment_id}/download", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/attachments/{attachment_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/storage", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/storage/quota", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/attachments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/providers", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/external/{provider}/start", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/external/{provider}/link", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/external/{provider}/callback", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/csrf", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/login", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/identities", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/auth/identities/{identity_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/logout", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/change-password", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/me", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/auth/me", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/admin/backup/info", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/admin/backup", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/baselines", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/requirements", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/traceability", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/verifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/diff", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/cache/stats", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/cache/clear", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/cache/cleanup", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/cache/performance", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/cache/recommendations", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/cache/reset-counters", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/cache/health", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/catalog", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/categories", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/categories", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/categories/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/categories/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/categories/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{requirement_id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/requirements/{requirement_id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{requirement_id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/requirements/{requirement_id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/custom_fields", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/custom_fields", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/custom_fields/{field_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/custom_fields/{field_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/custom_fields/{field_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/dashboard", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/dsm", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/requirements.xlsx", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/verifications.xlsx", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/matrix.xlsx", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/dsm.xlsx", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/matrix-links.xlsx", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/requirements.pdf", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/report.pdf", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/requirements.reqif", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/requirements.reqifz", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/baselines/{filename}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/bundle.json", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/exports/bundle.zip", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/groups", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/groups", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/groups/creatable", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/groups/{group_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/groups/{group_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/groups/{group_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/groups/{group_id}/projects", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/groups/{group_id}/members", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/groups/{group_id}/members/{user_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/groups/{group_id}/members/{user_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/home/attention", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/search", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/imports/excel/preview", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/imports/excel", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/imports/reqif", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/imports/bundle", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/admin/logs", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/admin/logs/stats", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/admin/logs/export.json", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/admin/logs/cleanup", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/matrix", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/matrix", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{verification_id}/matrix", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/verifications/{verification_id}/matrix", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/mcp/principal", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/mcp/audit", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/mcp/internal/audit", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/me/permissions", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/members", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/members/{user_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/members/{user_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/reviewers", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/reviewers", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/meta/health", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/meta/deployment", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/meta/build", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/notifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/notifications/unread-count", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/notifications/{id}/read", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/notifications/read-all", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/notifications/preferences", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/notifications/preferences/{project_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/notifications/preferences/{project_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/.well-known/oauth-authorization-server", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/.well-known/oauth-protected-resource", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/.well-known/oauth-protected-resource/mcp", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/oauth/register", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/oauth/authorize", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/oauth/authorize", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/oauth/token", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/oauth/grants", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/oauth/grants/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/projects/{project_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/archive", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/unarchive", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/project-from-path/{slug}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/reports/types", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/report_templates", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/report_templates", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/report_templates/{template_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/projects/{project_id}/report_templates/{template_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/report_templates/{template_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/reports/{file}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{req_id}/versions/{v1}/diff/{v2}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/requirement-version-links", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirement-version-links", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/requirement-version-links/{link_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirement-version-links/link-types", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/requirements", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/requirements", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/requirements/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/requirements/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/projects/{project_id}/requirements/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{id}/versions", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}/versions", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{req_id}/versions/{version_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/requirements/{id}/impacted_tests", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}/impacted_tests", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/requirements/{req_id}/versions/{version_id}/approval", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/saved_views", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/saved_views", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/saved_views/{view_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PATCH", - "path": "/api/projects/{project_id}/saved_views/{view_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/saved_views/{view_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/semantic_search", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/requirements/ask", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/requirements/reindex", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/index_status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/semantic_search/status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}/trace_up", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{id}/trace_down", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/coverage_report", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/traceability/clear_suspect", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/traceability/clear_suspect", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/next-code", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/use-cases/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/draft", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/use-cases/{id}/draft", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/use-cases/{id}/draft", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases/{id}/draft/operations", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases/{id}/draft/publish", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/versions", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/versions/{version_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/links", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases/{id}/links", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/use-cases/{id}/links/{link_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{requirement_id}/use-cases", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{verification_id}/use-cases", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/draft/diff", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/use-cases/{id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/use-cases/{id}/comments", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/use-cases", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/use-cases/{id}/diff/current", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/users", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/users", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/users/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/users/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/users/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/users/{id}/password", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{verification_id}/control", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/verifications/{verification_id}/control", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/requirements/{requirement_id}/close_out", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/close_out", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/requirements/{requirement_id}/compliance", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{id}/snapshots", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verification-methods", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/verification-methods", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/verification-methods/{method_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/projects/{project_id}/verification-methods/{method_id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/verification-status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/verification-status", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/verification-status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/verification-status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/verification-status/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/verifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/verifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/verifications", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/projects/{project_id}/verifications/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "PUT", - "path": "/api/projects/{project_id}/verifications/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/verifications/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "DELETE", - "path": "/api/verifications/{id}", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/verifications/{id}/field", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/projects/{project_id}/verifications/{id}/field", - "variants": [ - "server", - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/register", - "variants": [ - "cloud" - ] - }, - { - "method": "GET", - "path": "/api/auth/verify-email", - "variants": [ - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/forgot-password", - "variants": [ - "cloud" - ] - }, - { - "method": "POST", - "path": "/api/auth/reset-password", - "variants": [ - "cloud" - ] - } -] diff --git a/docs/developer/api-contract-source-review.json b/docs/developer/api-contract-source-review.json deleted file mode 100644 index 7f587699..00000000 --- a/docs/developer/api-contract-source-review.json +++ /dev/null @@ -1,378 +0,0 @@ -{ - "frontend/compatibility.json": "0d9bcff76abe3a44b59c5bbcd3b73acd29266394e25c16b7c4b069bec28b9472", - "frontend/src/api/attachments.ts": "3ab5a6039eea83e9a84c1198e686802edbac7812821eadad84050da3a0b40089", - "frontend/src/api/auth.ts": "8b43e8187605037285550f751588ab4ed3b36b7813babd0e7ea397e9cb893f0c", - "frontend/src/api/baselines.ts": "2048ad67ce7d5bb56d6762e13344423e599b7dc71b287b26ae9dd0c07c2be022", - "frontend/src/api/catalog.ts": "9b80cbd031a50fedc09abc09f9784506582d68715ee92b56b4066faa1ab9abcf", - "frontend/src/api/client.ts": "ab4787005a7a4139996d857c4bf06414d8884d4462e3702452f9c1cdcf24df66", - "frontend/src/api/dashboard.ts": "2de1b33077d0de43ff695c222b90870bbda4916595332f80249796aac2f93c1c", - "frontend/src/api/exports.ts": "8dc5817e40727ced7ffc9b9f485b92bbf716764f22c3a48d0925a07a5cc48a19", - "frontend/src/api/groups.ts": "3e58295865acecb4f0fa18be7a5c490d7a011a4eab1bd771f2e6b7cfaa07fb2f", - "frontend/src/api/home.ts": "2454252b15757c234a781b6d14559d10f73ec0a468defc77c0407fa8877137ff", - "frontend/src/api/imports.ts": "72a998ce604f6b23158786b5992cf7add1ab2be532100be4b22ed9e77e6cd4d3", - "frontend/src/api/logs.ts": "9822d3be5e25aeb397fc6993740b9eeeaebca68262619e13e35f7c5d9ec95b44", - "frontend/src/api/meta.ts": "e4441b4ea3a89e65589e2d837b1f4a4e404061d3c8182cfc6b7839fe40c46e9d", - "frontend/src/api/notifications.ts": "35f6cdb54f1f1ebb229368d1a667ef1a3038600cf5f11b0614b5cc83a8e1ce89", - "frontend/src/api/projects.ts": "6bdf2aab505c5c422450fb6d375307d3fc6e01de4a5430f898ec84253a3fff40", - "frontend/src/api/publicAuth.ts": "207d53b750d9065911365f2a96c4d3a4a110983123b264e19b96da3ea0fd74ed", - "frontend/src/api/reports.ts": "1fc085f17031c28a011288dc6d85c5a87bc723b5007132f1769b886f41dbe560", - "frontend/src/api/requirements.ts": "9935a13bcc618a5b95be7d4a0783456d9f5fd9d32c3391964cbb2d3731471d78", - "frontend/src/api/savedViews.ts": "95a7dc49f2b7b217fc63d1032afc064b9a6aa852e513bf468a0f983073197760", - "frontend/src/api/traceability.ts": "7dcdab37cd865af634081bc8df0b05dab225e4df124f59fa8c9b526f2f99b22b", - "frontend/src/api/transport.ts": "25db6fdcde2a1c5c65e08dfce1a92f21e04b7b4b9a8ec2f613bbc0d2b63bf59a", - "frontend/src/api/types.ts": "9b4b6b7b71730b6ea0d214dbe087c264b864df91930f78ba88a7ed77be361489", - "frontend/src/api/useCases.ts": "1867c3aba24d4883fa243299067fff54abfcdb77195f771d4cd6bfef24c7832f", - "frontend/src/api/users.ts": "7155f0bc8f6768753d6a766ca25145259e0fd59c1321870f2967821e4c6151c6", - "frontend/src/api/verificationControl.ts": "f372bfaad0f15c7b112798ba54298776cb8993a9bfb41abde4dd3490974e5ad2", - "frontend/src/api/verifications.ts": "09315bcd631942a15aa01cf6d35e951bdf09c38f045572a50d86fade5c6b4042", - "frontend/src/components/AttachmentsPanel.tsx": "864c91baffb45c90703a7d6f5f4e7dd7245887c684e24b4931470dba39e42195", - "frontend/src/components/CommentBody.tsx": "ca3be1bd63cdac8761218a24e35445d33877e8d201ba56a4c99a7e82a11da5fe", - "frontend/src/components/CompatibilityBanner.tsx": "802755e5d6078469f55bc2fae084046d262709f1895e771e5f705a1efa766dac", - "frontend/src/components/DeleteProjectSection.tsx": "bed2e4b648e36d1a5fbf8a109a14279b87bb26d6ffeef445a971558c8d04734d", - "frontend/src/components/HierarchyGraph.tsx": "ea11fbe1e1eb949f23bc5913b9b182dabe796d9e0d8a79adb1a02d7474e7c7e1", - "frontend/src/components/MentionTextarea.tsx": "228f4f7b836b129a5a9d1e223b6c90f1d015fafee9c34d9626b61d569acc8446", - "frontend/src/components/NotificationPanel.tsx": "d787fad22b8bce38598991541da458f8b4c263131e07227e7d51df412f2b5c9b", - "frontend/src/components/ProjectCreateForm.tsx": "4432de404b310f026d9ad59eeca998b9b5502fccdf985f23a7afee95b560a0d3", - "frontend/src/components/ProjectGeneralSettings.tsx": "151ca068c4911be56f42ad14fb12530b95ac5c353939e432f2f5c18e54750358", - "frontend/src/components/ProjectStorageSettings.tsx": "0b77e7fbeb87ca2fdf29c4b316d87499c0505b38af6f7ab743f57ee1d2d7b60e", - "frontend/src/components/RequirementCloseOutPanel.tsx": "c9dab4f0c3ed9a4477414faaf869adecbc934ed39c216542c2c8a75ad90e45a6", - "frontend/src/components/RequirementCommentComposer.tsx": "ebe202796ba001fee0d63b492236a358ec8ef7d80fea330f5c2f342a2742b531", - "frontend/src/components/RequirementMatrixPicker.tsx": "814990316c34b18594959134663b4b1ffc27f0f3b12acf1c4d7a39db746d6400", - "frontend/src/components/RequirementReviewCard.tsx": "2cb83ac3cd3cc78a407c40dbcd56a127aa4426d6eaecb70a02a336331c2a7118", - "frontend/src/components/RequirementVersionDiffDialog.tsx": "80a6c1aa4b30da3693ccfb9614b05fad9a57fab40cbb9b652308b3235345bbb7", - "frontend/src/components/RequirementsTable.tsx": "c88067b10d2debecb6a1e887d1c5eff8be40a08f70191d69d1bcfe8590a377a0", - "frontend/src/components/SavedViewsToolbar.tsx": "b0dec9ba27a8055cb07bfd79d1c54f512432d1de67e314312125e8292425cfad", - "frontend/src/components/TraceabilityGraph.tsx": "6c74f0abc03db92a55c8b1d670185ae69fbbc3f5999d9cee409ac6c5c044de23", - "frontend/src/components/VerificationControlPanel.tsx": "994b105d7a29e3d8fb9bbe9d0dd78f97377f416415e00ba12a90a64027e94550", - "frontend/src/components/VerificationStatusMetrics.tsx": "0cf513fadec90402790797faf3a158b7c03c696c621fe0c11a3dac9c1fb21428", - "frontend/src/components/VerificationVersionDiffDialog.tsx": "532ff63107b952943f516cccd08e4bb2547b101db8b61c6cfb8814a7dcaa074f", - "frontend/src/components/VerificationsTable.tsx": "7712820673fea66961898d70ab98f60b46e823fec0370db209bce54ca7119398", - "frontend/src/components/dsm/DsmGrid.tsx": "417d102c6f81f220f026dfac83181262688d45cefab60a24ead6c4fd6104a4c4", - "frontend/src/components/dsm/DsmSidePanel.tsx": "795b7d6b208b8ac7bc65c473deaf58f2e19ff40a2d5496b35e82da481c00c186", - "frontend/src/components/dsm/DsmView.tsx": "f478d1cebd6d1ff7cad418eb9953c6dd7619b412ca33219176a1fb69eb02ab1d", - "frontend/src/components/matrix/MatrixGrid.tsx": "2fff2d35fb634a7f5e1e2f08a8e63e75b12ae91e0fdddc6e8abba263d40cd759", - "frontend/src/components/matrix/MatrixSidePanel.tsx": "e12b080999f75d1e5b93f4f0ad77c22582807a06e41807ed86cd78c7540327ee", - "frontend/src/components/matrix/MatrixView.tsx": "8b173ad6c53f9082e8a4c28db5d6d65cdb26faf947d07426b28eaebfe3dcaf0d", - "frontend/src/components/reports/DocumentSettingsForm.tsx": "5e5bda5e256db2cb0df6f6745a0f56059cfdb9df8cda31d668103cc3a6919d1e", - "frontend/src/components/reports/ReportDocumentsCard.tsx": "ea4599e550fc798fd6c55b62788f01f503c5b0482834e7e2d4128bfc0caec4b3", - "frontend/src/components/reports/SectionList.tsx": "7adb1c33b62f3ef05e0db15af981219799f1e993ebf4da5c73d3aa1f81907bc4", - "frontend/src/components/start/StartSearch.tsx": "0cba50200262e52bda2fbf22e7c57803e1d2db0c2faa60eb5587d8af4af2a167", - "frontend/src/components/useCases/BaselineUseCasesSection.tsx": "b4d20fb9a89f5c730379aab6c3e4417133b38f700f39e79a4e64c7cad202a822", - "frontend/src/components/useCases/BranchCard.tsx": "2c16c584c566340617833503e180d2be175e7415761d8455b9457061834fd4f2", - "frontend/src/components/useCases/LinkChip.tsx": "0ca3ea3f0c024c85eec9d5d6dd2ca4525f4d06b5b74d021b2a72222295590ca6", - "frontend/src/components/useCases/LinkDialog.tsx": "0db09520c092df0410e9e65231e94e4005daa64c41fd56b7ba180c758e3fa782", - "frontend/src/components/useCases/RelatedArtifactsPanel.tsx": "03fbafa70a47258bbe715c9d2005bf26a6bf677b6d3926bb98f6c9582661cfdb", - "frontend/src/components/useCases/RequirementPeek.tsx": "09d3b1de5afa65ab1ba3e3add96fe3cd9ca12aef944c5ac29adffa6a91a351d6", - "frontend/src/components/useCases/UseCaseDiscussion.tsx": "73f54e3c66cc786226fce62764c7221d34895dcb46fd004d60dd0901a7954544", - "frontend/src/components/useCases/UseCaseDocumentView.tsx": "46957ce3466f1a0d4ac45112c1229a2c68fd428fbe515d1681faad3fced4b405", - "frontend/src/components/useCases/UseCaseFlowView.tsx": "ffb2b6acfdebc2e4fdd5df4b6f1ba974d07aa45556b57e4124d505257dedfe22", - "frontend/src/components/useCases/UseCaseReviewView.tsx": "f642c462715da9f95cbe0f0791ebdbe77bab97eabd791ba41abce87048dbf583", - "frontend/src/components/useCases/UsedInUseCasesCard.tsx": "cd65c36495771faac75eb6104cc5cc35c50c22c42247d3c8db7d6208bdd2a427", - "frontend/src/components/versionDiffUi.tsx": "288799d997ee305283411d9637910a4a9eafa6d57d6416f3ad7b14b1f7915a72", - "frontend/src/context/DashboardContext.tsx": "fc5b37e22e5c758062ade4f23474186d06fbfab39830ddd6caf4b56340762a81", - "frontend/src/hooks/useBuildInfo.ts": "444733da6fffd5734a5511370d1ed3253ea6d05b2425d91bbba0aae4fed12b77", - "frontend/src/hooks/useNotifications.ts": "ff1cc4fbabddfe4f91c1dd98231dd082c5eb812249020d577ac8962abb383124", - "frontend/src/pages/AccountPage.tsx": "2c6b72c059ed8585e92a7bfb3cbd6ec01066801d27a00fe1ced770671cfaebec", - "frontend/src/pages/AdminPage.tsx": "7cac5d4dc76bafc498831509c34c8870f774bb75cc9a816d63e48f3fab2d1a98", - "frontend/src/pages/BackupPage.tsx": "aa5912d7f6e4b76f7759b790951dfd4fb5b0748261ca928488cb590465abe8cf", - "frontend/src/pages/BaselineDetailPage.tsx": "590af60312aa864a7bda51be8f85e04c2af90edcd3a1e1bead7e74c497ba53a2", - "frontend/src/pages/BaselinesPage.tsx": "efec445a6c5f8b8062b0817cd49673519a6da4e401d353d81b802dcd95b80fd9", - "frontend/src/pages/ChangePasswordPage.tsx": "0ff12abd9c144cfc2fa1528cdc3b4076b00b686bba48c7ddf567ee6a0b4846ad", - "frontend/src/pages/CreateRequirementPage.tsx": "b7b87777c9438ed186c9bdca1f7533e4b8d68520efa3560c6edf13f64cb75cef", - "frontend/src/pages/CreateVerificationPage.tsx": "c638e2d1e8be63854f0acd4b167aa575fa85b18119ef10b2684d0dbd3f2c204a", - "frontend/src/pages/DashboardPage.tsx": "5ec394dbe016542d4b85a031aa589aa360e9ab2e336e6eab6389fb763c65e806", - "frontend/src/pages/EditRequirementPage.tsx": "acd9281739516a859e32111db4bb5a224e529ad6b3f77e77d6a5790b036278b4", - "frontend/src/pages/EditVerificationPage.tsx": "f9f7a455dd564269e3d02be7e7f156aaa27a37c9b41e0520c42f389891d145d4", - "frontend/src/pages/ForgotPasswordPage.tsx": "c8beb4f6ab9869c01f6fc3f08f2b624c6f2828e41c9433d285e0b2827dacde45", - "frontend/src/pages/ImportPage.tsx": "3d64cecd82bb0ac326202940401155aeaee3e24da2b6fa1f2f9f6d557db8cb07", - "frontend/src/pages/LogAnalyticsPage.tsx": "b5e389677fe998fdd60e0daca5290de48edc17904cd07876c51ca317d4ae3e28", - "frontend/src/pages/LoginPage.tsx": "574ca76f3b7d20021aa66ca786fd7a46b088e340710dc50f658b0ff0c14a99be", - "frontend/src/pages/ProjectBundleImportPage.tsx": "4162d83cad0c9633ba392bf065edbfd93acb9d99ae64d9ddb4506828f2a2d525", - "frontend/src/pages/ProjectCreatePage.tsx": "17e145d53aebdbde58753a27b093c5b7a70cb109ca507d6a4f2c504fa0743317", - "frontend/src/pages/ProjectLayout.tsx": "3bfc38ff8fe368dc8ff25ab05f5f6acb5ec37a33aabfb4e10d5ba8e849a9322f", - "frontend/src/pages/RegisterPage.tsx": "1e9e057dde4e9faf2c39e2b5a1f8aa4ea8f6c01ee802006464c78bc2f5e40f88", - "frontend/src/pages/ReportBuilderPage.tsx": "8f5d61e7801a1a2deb1b5e51108fec0380bd4d83dc6cc839820d818696d50f3d", - "frontend/src/pages/ReportsPage.tsx": "1b37d6a6b5ec8b8a5fe116f7f8fc070a95524f36b1b5373a9aa76e7fc1abaf94", - "frontend/src/pages/ResetPasswordPage.tsx": "bf60116c75c4178d98cb52a25e4c74fce8cfe7b01f3868357a055f95262ddf36", - "frontend/src/pages/StartPage.tsx": "92e1ab484cf0c151118aef27ab0278f9f9a57a141e7230f2572a493830591205", - "frontend/src/pages/SystemLogsPage.tsx": "cd505938abd22298c9eff02316c2e1d12fc58aeaecd0ba07c2d2fb8640a4f537", - "frontend/src/pages/UseCasesPage.tsx": "8b7bd84008cdd1005f7177912e23306951fb58f04ca070f217a230fcb2e6c80e", - "frontend/src/pages/VerifyEmailPage.tsx": "d12d0483bef2b7f1242b70cd49de89671ec39e04e557e5183788cf3b0e85a0b6", - "frontend/src/pages/ViewRequirementPage.tsx": "32cd5e22c24aeae559c87f06a7f5daf7bced700fb1cbbbfd489c98e84739b2aa", - "frontend/src/pages/ViewUseCasePage.tsx": "1cf82735d28145d518dc3d7b9f4970c6a9afe735707000c511a2fee85e3c65af", - "frontend/src/pages/ViewVerificationPage.tsx": "333753fc8f69e2c69579097191e1118fdcc2b51309c0c915e394775aa7560a2c", - "frontend/src/pages/account/ProfileSection.tsx": "4853064f8b07d51cf97c2437b021fec43c5e7bc21ae54f1979242021cfda9d0c", - "frontend/src/pages/admin/AdminLayout.tsx": "fcd781952701cf7b14a34077669d15e0daad07aae8f7318c9f5d61969c617849", - "frontend/src/pages/admin/SetPasswordDialog.tsx": "66dec75ff74ae92c46ff6539786caccdd7075b5182b85ff022772dc2a5493729", - "frontend/src/pages/admin/UserFormDialog.tsx": "7b90758cf2349f92173da92a31a250aa0cb7146e4888dadf862cff611b5b9571", - "frontend/src/pages/catalog/CatalogApplicabilityPage.tsx": "3e0322240e4da2a13854fa061207987dd97f65d6efb016102a6335a6977ff804", - "frontend/src/pages/catalog/CatalogCategoriesPage.tsx": "c4819f09441e4d435f6b2970ccfde47bba93aa9a8bc27c2f6741962d8e8e2176", - "frontend/src/pages/catalog/CatalogCustomFieldsPage.tsx": "48009a19ac670cc45c79894fa425eb9c6b1e9e7162dec18fb7a9ac1f59c3e390", - "frontend/src/pages/catalog/CatalogRequirementStatusesPage.tsx": "2e49517a487c93a32bb493ef5455073da81000b8378cb2a7a80638f95185d4c3", - "frontend/src/pages/catalog/CatalogVerificationMethodsPage.tsx": "5034a088d804caf187109a9709cea94e1773166b6e55b48ae9a2c1c83c2f3fe4", - "frontend/src/pages/catalog/CatalogVerificationStatusesPage.tsx": "8a24da86a3a6d98d0dafa4f1ddd201dda014880ef420502ae61f2c02796cc231", - "frontend/src/pages/groups/GroupCreatePage.tsx": "ef599da27c8b439dd77899b5b6c7e9378be83af68114da5cf321c3d5f7f6edfa", - "frontend/src/pages/groups/GroupEditPage.tsx": "16c192ed8e7c060f5e32a701c2e93ca4cd03e0431e381c13c999c127996ea995", - "frontend/src/pages/groups/GroupMembersPage.tsx": "29a3f2c44b8c374b8569e49dd341e8e403cfb0125e40124409f469ffe9ef4aca", - "frontend/src/pages/groups/GroupViewPage.tsx": "ab42dc3302004af815427e247da6c66ed9485d3190b56690eb8687a84d72884c", - "frontend/src/pages/groups/GroupsListPage.tsx": "8ade08bf0e4815ee866e4bbbe1de1a52592de1cba57659e52c69a9b44f6d4530", - "frontend/src/pages/settings/MembersSettingsPage.tsx": "8a9089ac90817ad47a4306b12081e7b262142082c9a545f864f45700629e44b2", - "frontend/src/pages/settings/NotificationSettingsPage.tsx": "45a06bcbc7c68580e11a1dfc10d92a71fa07d63e8636c2f70db0d2e9fa37db4f", - "frontend/src/pages/settings/ProjectSettingsLayout.tsx": "59eaadac194a13684ffb2285cf2f6d9bcef3d781e9433eb3a6ac439c5b188383", - "frontend/src/pages/settings/settingsContext.ts": "43047b5fda62fa4131d7928842ddaf6e7600c90b684abdc38b8c71ee918765f3", - "frontend/src/statusAuthorDefaults.ts": "fbcbdbfa7e1402ce194a0796a665bb8fc7b22da209cdda630b6e19798c67306b", - "frontend/src/test/reportFixtures.ts": "126979fb70781d2c29175510b5162c37eb62442ea66a2f60243821aca68fce60", - "frontend/src/utils/dsm.ts": "f9248410826e11d2eba0acbadac7877675e91acf7d9e17607409c8db4e561c52", - "frontend/src/utils/duplicateRequirement.ts": "dd9ae9e091d773f2dbe4edd75afbc548e8d90f71c85dfda9be974073703ee4bb", - "frontend/src/utils/mentions.ts": "ac1f4b8a4ffc2086f945315e40b7001d03c48ffa42a93875d3f09ea06cb110d2", - "frontend/src/utils/parseUser.ts": "528441744ab75019bb683f3cd2e5d142abe425e63d4407592c1cbcdd37957a7d", - "frontend/src/utils/projectUrl.ts": "146123b4a0a460339350dc7ab96dde0649623c7a67cff68997fe005a6e4cbf2f", - "frontend/src/utils/reportDefinition.ts": "f39b5528889f7bd0fbd5668e5cc8eabc296db77cae433526dccb036df913167b", - "frontend/src/utils/savedViewDefinition.ts": "a1d1251f7d60ea03c4b3a8a55f75722e936bdf30dd6f370b836fa42fba3a0f7c", - "frontend/src/utils/useCaseDocument.ts": "a51556ac25f9e5bd3c4c92328a86738bacf2c45a0a42608dd858821f3c492de4", - "marreq-cloud/src/api/auth_public.rs": "b080c11b42d95b02a21b6f49226bd1c1c9f72a6eec4f2056f6a7f29ef4def378", - "marreq-cloud/src/api/mod.rs": "0a570340b80f76ced32d7f30c057c16d95dad39ef433180911f7a8225da18919", - "marreq-cloud/src/deployment.rs": "6833286dc0ceb2933e95a1bb884a5174548bd801b645b97ee05d0d7ca042bd66", - "marreq-cloud/src/fairings/cloud_admin_bootstrap.rs": "d9cad7b7e1c71c071696fee5bbabd387ca438e741a821e577b26fa0a8c65ea01", - "marreq-cloud/src/fairings/mod.rs": "6c14ec5bd25e8835c4b19cbdb1674a3540ad7cf106dcd43c136e9ddc263db272", - "marreq-cloud/src/lib.rs": "8f873d14051b1b1c177bb9b66eb81b9430f970073219d8cff8deb7834c2cf4fc", - "marreq-cloud/src/main.rs": "45f07e434e587a3c8ec95fec94b127d43fcbb395ae9da6351ea4115ba07dcdcf", - "marreq-cloud/src/routes.rs": "36ba1db8aa241bbd96757d341bf2058e2bf650019242ee2a09ee90d03b833905", - "marreq-cloud/src/services/mod.rs": "4607f9a7c5c8651472b8016b6ac8f68ca3431939ee4c3f9d0e944422f7348a25", - "marreq-cloud/src/services/registration_service.rs": "433de11216e66c659fca1572d2c9326c7f27b58076a6c65bbc92d4bb876eef41", - "marreq-core/frontend_compatibility.json": "3e569883e23b5411ca13570743096c893cf876362060007d0a17a8ee5623d2a7", - "marreq-core/src/api/activity.rs": "94a3fd9c6aa73ac989935925f108d35db55b148cebbc6dfdcde62b8872d1809e", - "marreq-core/src/api/applicability.rs": "aa88a53ccdeda77df4d83f7bffde00668bf6152ef1e7b598ca9179ecb472b081", - "marreq-core/src/api/attachments.rs": "a7f600d5e6293038ee668ca673ed093be06714e1191a06b640bb99cd11f56124", - "marreq-core/src/api/auth.rs": "42c42fe249ee62a7e5bbceb05030bb63f50806ba4c8b9ca36ceaa49b5e372ba1", - "marreq-core/src/api/backup.rs": "a364be4c85072f311d0a8e0930ec42dae25182ac035cbe4ee9576e2d1d74cbe6", - "marreq-core/src/api/baselines.rs": "53bee136e032cb6f167d639c06d9feb7f230378a88a5fee6de5819611b3c239c", - "marreq-core/src/api/cache.rs": "63410e53b7e2c01758bf565af66981dbd5bd046fd222d5ea9c6b5e4a6f571c75", - "marreq-core/src/api/catalog.rs": "8a7017fff0a5faefe8d124aacc0ed3f1a68c5289398969391bec3d40c9cb53ae", - "marreq-core/src/api/categories.rs": "af9ebb58df5c50e7d1a590006400ce2f5113d846b481cc5e32b6a1a0d0f070b5", - "marreq-core/src/api/comments.rs": "4d5429530e90a4f9fb4cf5b7c224c7744a5c89b4dd14d6c5aa11c4b4c09e161c", - "marreq-core/src/api/custom_fields.rs": "7138b0fee7f5c2a938c277413f7bd8289c4ffaf9ecf70635f9eecdd4d3c5b48b", - "marreq-core/src/api/dashboard.rs": "d262e1de00a6b180951ac80891c6ed2574a5b96f939b2cee4bf21c4a80952665", - "marreq-core/src/api/dsm.rs": "609ccf610c64d69438c28fdce7623cf81ff68a1f91f0f7f9755ad0985d81c474", - "marreq-core/src/api/error.rs": "f8703993e7a5c266b2cb7b946246a5cf285a46ddda9d4f976e8a1d0e4f6c3d0e", - "marreq-core/src/api/exports.rs": "ed842ab4cff67fd556e779bf65744e4bf1d42e8bd3513a10be4cd3a1731ae40b", - "marreq-core/src/api/groups.rs": "3d8f31614abdc70bb2f9dddb08d1b8464556d5a04fccffd16c337692d91a60ad", - "marreq-core/src/api/guards.rs": "ba11edb5858a725a36e1988a7ea76d1ac0642945bb6c3f8406eee31d00e7deda", - "marreq-core/src/api/home.rs": "3b8f325d7f1096734378702bf42c72e8c7adb0793b7c77ce51478b8f6a18dfbd", - "marreq-core/src/api/idempotency.rs": "7586819b8488daed5c7fb51495c70c9369a8cb069d1f9aed76cdb0ebc12e6f8a", - "marreq-core/src/api/imports.rs": "d76d3923cd34f58f02b55a600c56e2491b4b14c8269bad23c9ddd137343e518b", - "marreq-core/src/api/logs.rs": "126db136b1bee0f78227cf7be98fd330309700ecf6981acd4fd1dfeac74bfc2a", - "marreq-core/src/api/matrix.rs": "951a83ff3feaef5b94b902b8101779873d5075eda609946376a7668338d02f22", - "marreq-core/src/api/mcp.rs": "01451ffb92308c53634718d763433e60b1714c42f2f02a33d3e1a7c46338a0cd", - "marreq-core/src/api/members.rs": "398c1bd74615b611c6fd71cb3d54b2c26c077b90d0db5b52ea8197fe1fb99a83", - "marreq-core/src/api/meta.rs": "13ff923c9635214132f8e5ac6be236fa9e0dc0eddca7ea826993655c4ce0fb2f", - "marreq-core/src/api/mod.rs": "a8b106368e1fed4766fc75c8135f5ec63e867452e18afa096afabd66012d1a2c", - "marreq-core/src/api/notifications.rs": "ca1f959e06e5f8e8d8e55cb4d83342006378be4c778ac92cf67e1c885d9b0a74", - "marreq-core/src/api/oauth.rs": "ce1a150b3c3b3ef2118de60767f95ac502000c29ae94520b5bbf1d14202eb801", - "marreq-core/src/api/prelude.rs": "3aca18c037023d5076101771d0c32045fb8cd2dda3d8410bdda9f2b6241ef9f2", - "marreq-core/src/api/projects.rs": "e607a66955222dad5ad6fb29daf8c881fc730d12fbc4c391bdebf7664d97ca2d", - "marreq-core/src/api/projects_session.rs": "06a1689822c8956ff4570931cdc056a198e0bfeae85b6e171ea11546d8c3c840", - "marreq-core/src/api/reports.rs": "09c31e4c57508f6aec4b042d86e87765690f82ef64a5c6b0fde233fc2a190e10", - "marreq-core/src/api/requirement_diff.rs": "39a662d096001564033e2069c67265b797d5b7c5eee45bfe26cdeee1c04cf637", - "marreq-core/src/api/requirement_version_links.rs": "a2790428840fb0c99a874f26ea524b6649ab3760b5218b8bf6ea20c0d6510848", - "marreq-core/src/api/requirements.rs": "bf2317f5637bedd763f6a37a9b015fce0e064c58315530a0f6430724606df93a", - "marreq-core/src/api/saved_views.rs": "ed3a13e3d95d56b1a6a2f41e123a7dc6944ab1b534845c3c8309de6c9f111d6f", - "marreq-core/src/api/semantic_search.rs": "6e4da1230a89137f3339fab09750ed9b8b773a3eb2722680909b32fd69a92ccf", - "marreq-core/src/api/status.rs": "4c1ed40c20c74cf541677f8573b60af5afd4eb32decda9a4d9d10b69082c6299", - "marreq-core/src/api/traceability.rs": "b45e4e0ab57e9fb8da0d4bb58e32945b4cb8cf9d2f175d548502360f411807e4", - "marreq-core/src/api/use_cases.rs": "3355a964773df4500bb173bac7b1d78ce9e2110e48c6e628bfa3f6f249b1e766", - "marreq-core/src/api/users.rs": "98bc2ed2ce937ef745399178f78b59517ec10a8677497ce4f7c8a5c6374bbe66", - "marreq-core/src/api/verification_control.rs": "5f49241dc2ceae386381c3c5a088e75e7e67d5c5140ef8cbd0540c021e83882c", - "marreq-core/src/api/verification_diff.rs": "3c1957bddba35bf6a3c88e91e02353584aaf52dd8fbb853ccdef812fd2fd2fc7", - "marreq-core/src/api/verification_methods.rs": "092ce0a633249acfb9f387ab859bf832512bdbb68277c906084e8929c11ba58e", - "marreq-core/src/api/verification_status.rs": "91fe8437410198511618bbc7638480be7c0e6dfa1c63ef67ea45733dc070b746", - "marreq-core/src/api/verifications.rs": "77e5d01401363a2d0a26664b9015ca836261a108aba631d9d2a6b5468b285f71", - "marreq-core/src/app.rs": "f506f1951a8db3660348f69630a7e77fbe5109a0365f0a26fb49fc0049b50cbd", - "marreq-core/src/auth/config.rs": "fe55c128b1c8886451a45d5b0863840005de6afda5f16b77573512885e33fbde", - "marreq-core/src/auth/csrf.rs": "a988dc55f7cdbc390072fcdd91fda6f43233023c85d05a04c036152f95ad9b0a", - "marreq-core/src/auth/delegated.rs": "61a549874674294a037394625ac81b05844f88627b5531cabf812bf15174c3dc", - "marreq-core/src/auth/errors.rs": "acb72478d2c6bd08f59efeed13d01d484c46a7024dd791c65910948abd7f022d", - "marreq-core/src/auth/external.rs": "bf0c4a33b3b849de41a31c7761f9478eed37f55e6e320fa5640a6b050f0b9bf5", - "marreq-core/src/auth/guards/admin.rs": "bdc1c1654926cb9b5425897371c87b402393d2d5c1fae29cc8fc37f40f3d1df1", - "marreq-core/src/auth/guards/api.rs": "ffdebb6107730290213f249fceea8a5796089339ae5a8d8a61014d074917150c", - "marreq-core/src/auth/guards/bearer.rs": "138e9c96e04372e8f2c3f33c298146bfbe128bf53ddd94e147a27856b41ee7b7", - "marreq-core/src/auth/guards/group.rs": "2042fe1d4af6350d0eb8a42e23ca5a35452cc8e9bd80535cae13154f0cc78f48", - "marreq-core/src/auth/guards/html.rs": "21529d3a0d23ac112f57d8cf7be81bde9824a42a9d44ad48bd91a0a2798b2389", - "marreq-core/src/auth/guards/mod.rs": "09e01eb7522a013007167447d0102fd7173d8588e1803fbc406748eeb5dda7c8", - "marreq-core/src/auth/guards/project.rs": "3a03e405327e02f35f1b69348a03b0ae1762d0e19f24c9f46acd0bb69da341d0", - "marreq-core/src/auth/guards/route_params.rs": "e076f960e7316ab45e33d78419296dc56ffa1020dcce95711f9865bbc4620fd1", - "marreq-core/src/auth/guards/session.rs": "b290b71d9d88f340f955b205cefc9c726728ff887b4014b803573fb173c39563", - "marreq-core/src/auth/login.rs": "48b258c2467fa4b7b82f07d9fba566be8efad302fb92ec445580ee6cc3705326", - "marreq-core/src/auth/logout.rs": "04e469e64e7bde1c21e5aca119f2ca166e28c747976b7ba494550275b65e6d03", - "marreq-core/src/auth/mod.rs": "ba9b96813124d3c439033a0fb5ff4815d48f0383bc05de9878bbfca8cf92938b", - "marreq-core/src/auth/password.rs": "1babc63db0d5adbccbc5aa94d378ec1b5a9ea962a1a40b642f308a44f75c441d", - "marreq-core/src/auth/password_policy.rs": "038967fae772a36137fc236b4e618776ce7a21ce53b80e20c1b5746919b6b9f0", - "marreq-core/src/auth/provider.rs": "cf7dd4fb37f566af283bd876cc0ac492b5c4d1e275e70c8f3b170e682ce8ebb2", - "marreq-core/src/auth/rate_limiter/config.rs": "9c225c383fd2fdb444199103a3dfc8ad892ae387c073162badfe64dd1df7713c", - "marreq-core/src/auth/rate_limiter/mod.rs": "f57a3c416486907adf7340d86058352046b553a1fc5a8de5b9d4e2d45913e8e3", - "marreq-core/src/auth/rate_limiter/postgres.rs": "4324ccc8fa90bfaf1e79bd44907f758bc2e07d72a4d34839b71afa7ade845428", - "marreq-core/src/auth/rate_limiter/store.rs": "7b8e861c56f62331d05996b7dd74d8f0e970fb1ebfadfc136e3a4d5e9220c0fd", - "marreq-core/src/auth/session.rs": "5c1df56bf8fc67ef2782d9e2781074ee6a366add00f997ca4e67fea5d06485b2", - "marreq-core/src/auth/session_config.rs": "732703ffde7f9f651b51c47ff544089edfd4fdda6fcd11ddef54e37968c2f605", - "marreq-core/src/auth/transaction_cookie.rs": "de91b4949f98267b232a3b3152460053ab16f3315e8821bb7186e28deaa4b069", - "marreq-core/src/authorization.rs": "4facd135c6f2b3c9c16249d9ab63fe4cf9d1fed8afe2190fb9d403d671f7d44d", - "marreq-core/src/backup.rs": "7e0223d93afbfd52dc0395e023c4be7156cb2aff193d27ec00557731754b19fb", - "marreq-core/src/build_info.rs": "3f602ae76288c24acc8efd5e445a299ca3c671c4b69b1c2695adc68c184c142b", - "marreq-core/src/config.rs": "44b3bfd89821e6965da447dde30045b181afbdd51e6540e980ce0912d6d40621", - "marreq-core/src/cors.rs": "31aa625f8ed94d43eb41e4d29c2ce35a30b61256b0388abf53dc30d580fc23a3", - "marreq-core/src/db_types.rs": "080883843e7ae0facaf671951d3985fbf8614f956dbb24db4c3f1624cfb206af", - "marreq-core/src/deployment/mod.rs": "7ca21f6bb56fc06009f4193c4509eb4b44622825ba33287a002e0a2f6f9a3873", - "marreq-core/src/deployment/test_support.rs": "3678a6ea0d4b989b14e630c3a9283b9a88b949e4b256e74b428df076308659bf", - "marreq-core/src/diff/mod.rs": "de6112f9c9731040d1af1afb2637863ae16c814105caebef2ba16bb9efdde7f7", - "marreq-core/src/fairings/cache_control.rs": "3a27895c3f0e2f5c9d41f922ac786ed59848ba5911434212c784daf270f1516e", - "marreq-core/src/fairings/csrf.rs": "be695fea17b384d081b2aa3dafa2fc0934c11b33cee48ef2a16139cb8a396d9f", - "marreq-core/src/fairings/mod.rs": "bb30f3b994c05075094bd4d2b91e96145a9f4daff1fcf0dc2b06206029854cdb", - "marreq-core/src/fairings/oauth_challenge.rs": "d09b75d9b7a82bdf232700dc4f8eb66a30a5a524031558da32f5c3f4b22859e4", - "marreq-core/src/fairings/request_log.rs": "0c1e067a3977e5c9d7ab77f58f188724d5d8414ac362e10a61822c8dd4ccf839", - "marreq-core/src/fairings/security_headers.rs": "f97b003cfb9a73d42aee51c4e2dcf94064b779f638a424be917a95c4ef89deed", - "marreq-core/src/fairings/semantic_index.rs": "498f6e3a73719bc92b3818b855a837da300380dd8c868ae9562c401069fa3abd", - "marreq-core/src/generators/excel.rs": "6a1ff8ca77c14227e5e93b086c5cc051601d1b47305dfc0f781055915a8ae57b", - "marreq-core/src/generators/mod.rs": "c1ccb1039a1282c40f4813cd5cd1ea598449f539e303784f7ac27754fb7c42b8", - "marreq-core/src/generators/reports.rs": "97cd2c6d58572e65f18dd20cd3bf68a193b610173d962e37781e2f4a04550958", - "marreq-core/src/generators/tests.rs": "d75f80eb06e806fe094672c361284267128c5400e9ad986f7f30fadf8a09c1f5", - "marreq-core/src/helper_functions/decorators.rs": "a1e66680f74295c681054eeb2b00dfdf859c6fc41fbe77e1fba6d9d3b70b1f32", - "marreq-core/src/helper_functions/filters.rs": "cba543dae55a85d7e45c304483856c3adfc7be988d6268f7bc63e1d6e5300976", - "marreq-core/src/helper_functions/mod.rs": "4c8fa19cde6104e8df42dbc0e5d4393d3ee1af7b2c83777d2add317f880991bf", - "marreq-core/src/helper_functions/reports.rs": "2b7cbd806ac46dfed4a836fa62cc6ac3b14bdbf8f8e6bd7019e1b1b96562154f", - "marreq-core/src/helper_functions/utils.rs": "ca5c701668ca6859527d185cbcb5574b528ada66e6bde154f1200eeaff6b7bc0", - "marreq-core/src/importers/excel.rs": "6fdea2824c9650724056618c4d17f5e27b347f6169be2365e33df4e32c4d71ec", - "marreq-core/src/importers/mod.rs": "c737520bbde0e3ae1d3f7e8b2f66bad2a9508f1da01116f8c190d2ab3c8543e8", - "marreq-core/src/importers/project_bundle.rs": "538e47b3b288ac938c7f6063e8058524b80f7cbdef16ba4ac8a648ffaabc93fb", - "marreq-core/src/importers/tests.rs": "e0f5331e87fd77c7f468199e75f4d2437ba5726bc8c975a957be734ec9399098", - "marreq-core/src/lib.rs": "1455ce36de9647931b03fd18c1f4a1c82a6df41e2df40948805996fbc4a7da6e", - "marreq-core/src/logger.rs": "b6a29960712e7fa9ad9ebfa33c041daab6e5a80d350615237903921e17491e85", - "marreq-core/src/models/decorators.rs": "40bb2f62a4037fbaf9f23aef4400606f4640e5f37471a4a8923e0dc46f7bf3b5", - "marreq-core/src/models/entities.rs": "1eabe06f2e169a5300cef45f85e897a9611233b1fdc0ac743a9bbbee705ac5fe", - "marreq-core/src/models/forms.rs": "7dae5b25d0cda8f5d943df5e546cd3e12fe691872052d475dd8f023ed6402c20", - "marreq-core/src/models/mod.rs": "649f0f88778ac1a4145ab00a07d685277cc365ce4be75801b1f462ad1ef3cc1c", - "marreq-core/src/models/semantic_search.rs": "3221de526e270bcfac49df625d129d93df0849137c201ecd58b0b0afcc9d8370", - "marreq-core/src/models/tests.rs": "66760a34dd08e200871c6bcddf0cd3572cebfea7ea3c3a11e5d7ff5b85c9641b", - "marreq-core/src/namespaces.rs": "0b7c8e3d7c437e8ee50daaaa81d578d21f20543496801bea7032fb9a4b8d12c0", - "marreq-core/src/permissions.rs": "aba57beb9f1ae6583e3c79c07530085328befd1b2cba7a2d925cead7eca10a3d", - "marreq-core/src/reports/data.rs": "2a3611d90d886b15dd8fae7f054cddd79aa42898d7a8855d5c0c34ad1533f3f5", - "marreq-core/src/reports/definition.rs": "8131924beb38d768c09b2e8c64c535de325a215eee0a1104e1b055da5c1a550c", - "marreq-core/src/reports/mod.rs": "dd04e70a3dd01e41561d0cc986655d08b1c70707dac2100803a4e4d64f5c8671", - "marreq-core/src/reports/model.rs": "8bd9b9d2b37f50d6e5058b507734d50c5685b98f8620cbfaaa418316d5eb916c", - "marreq-core/src/reports/odt.rs": "1990b981599d0c99bb296877e8806c4635098497903a50b60cc8d58ef98d4bfd", - "marreq-core/src/reports/pdf.rs": "05dd88da08ca9bd9dbceafd09c1c4a978505701e9b60cbe3c99ca83a41ae0b46", - "marreq-core/src/reports/sections.rs": "edc74cc6432a9ae0b0fece0f3fe2590173cd50f491e691e2161a30a1f5b266c4", - "marreq-core/src/repository/cache/keys.rs": "4480d4a36550e69a86162d029b9fbd18cf0f1ef7d2a9ad9ec4fb7fef0dba63f5", - "marreq-core/src/repository/cache/mod.rs": "70280d28b9fe0df543ba951fac7249ad498b49c206536f2dda32ca076df98349", - "marreq-core/src/repository/cache/repository.rs": "06eaec21cd1b7aaf219b39790076fbf400e07d0172bbf4ea10cfc0b84742d60d", - "marreq-core/src/repository/cache/stats.rs": "6f7c20f039b29f6060a1d76c3e8f11aece1828406a347dbdc6f303d06c8182f9", - "marreq-core/src/repository/cache_middleware.rs": "700959d0e2161082f1e2df92b1fd7309f8869fcb05679fd22b12c8e76f504269", - "marreq-core/src/repository/diesel_repo/attachments.rs": "995a5bf18dd274109d235de35a9d9655cc5aaa0dbaaf734aff6e47ec3054f6e8", - "marreq-core/src/repository/diesel_repo/baselines.rs": "1b636f5785c91ae8801608da4af8b5de2991f38edfd447f3e3285810035ddb75", - "marreq-core/src/repository/diesel_repo/comments.rs": "d2fe0edc50e95a4975a804ac68576bfee6166d91ae6d393b736a690a77f1162d", - "marreq-core/src/repository/diesel_repo/custom_fields.rs": "2c79f7da0dd3329712dc526d9f275ae3b5529b60ce2c5a38b3823c341aaaf7f9", - "marreq-core/src/repository/diesel_repo/groups.rs": "dcf3ecbbf2366a732fe72fc722afac9b4584772b58dbbaf7dc3903add4da75e6", - "marreq-core/src/repository/diesel_repo/idempotency.rs": "b8245707ed7c35683d461cc14ee8e8952bb654b63d9f87da2959ce2b371f9959", - "marreq-core/src/repository/diesel_repo/identities.rs": "29d8cba985744fb801f643585cd81f2d6e762bd60d2a748857dcdbed45d95769", - "marreq-core/src/repository/diesel_repo/logs.rs": "828817d0a2163df7b0d22176eadea4757731dd7ae4b6e88549b6fc68b0c848f2", - "marreq-core/src/repository/diesel_repo/lookups.rs": "35bfeba0d637674d912b97c72100ef58b09cf419eec25c368d7065a3354f491a", - "marreq-core/src/repository/diesel_repo/matrix.rs": "e7a75f4d2dfe71183ff0938c101130403343ecb21283f6c490d1543ab46d04e1", - "marreq-core/src/repository/diesel_repo/mod.rs": "3bef7d6ac3c6a21da409b8a9eec67d819f7ecf81ec340383f997148a11c76082", - "marreq-core/src/repository/diesel_repo/notifications.rs": "c49f845b11f9fa64e23a637a682919140133c761d2a9f049ebbfe0d3bc24b618", - "marreq-core/src/repository/diesel_repo/oauth.rs": "7c616112874226213fa2ddaf66cde5012d5c1398c279403ceb3e7ef506c99eba", - "marreq-core/src/repository/diesel_repo/projects.rs": "5f1a9a0081a7c6674e74e9bfe35cb3d0114e434bf97944453b41eeb1958fb1a0", - "marreq-core/src/repository/diesel_repo/report_templates.rs": "3d4542ced77375a9dc9e7066f288338d3dc042fcec5193c2b5181c140d470f64", - "marreq-core/src/repository/diesel_repo/requirement_links.rs": "6775b67d323497e444149a3c45c8ccec11d87a602add7d001ecec4963e88239b", - "marreq-core/src/repository/diesel_repo/requirements.rs": "24a0e00ddc48062949761b431dd46741ed8d261a0f228df2727c99708a85b75d", - "marreq-core/src/repository/diesel_repo/saved_views.rs": "6051322dcc26e2f6c194243460f3f292d12893035deb844b098a48cee980b9ca", - "marreq-core/src/repository/diesel_repo/use_cases.rs": "fcec4e43d0d65a467daa6abc5fcce97582ca71fc78c2f0ef4461b70094c3bfa1", - "marreq-core/src/repository/diesel_repo/users.rs": "883cf4fe2a69a882fbf71b37ddbed93191cb20910c2fc5d75d8c2aed7d92197d", - "marreq-core/src/repository/diesel_repo/verification_control.rs": "29c63e62ab0a445eaf11e28756a301d1ede358005eeaf12fc95769e4e5911f90", - "marreq-core/src/repository/diesel_repo/verifications.rs": "4493d652fd759c63e6220e8b2ef17a08e547283180da82aba189b03f9a9caa9c", - "marreq-core/src/repository/diesel_repo/workspaces.rs": "4ebe8759cc12391bc6b0b7679f1bfba462518c467aaf5576f50e777d338d7622", - "marreq-core/src/repository/diesel_repo_mock.rs": "2af62bd62ae078aafff93e01e7be20096e5b86eec28e034f0b81f2673e348e37", - "marreq-core/src/repository/errors.rs": "9cf994d8f428567cd8f0ca5206ba0b363e555e4dfb018561968e2cc7081dce3c", - "marreq-core/src/repository/mod.rs": "13f6e6696619adef56e4b541399be903837c7b6a31df20f7d552055fe1694dde", - "marreq-core/src/repository/tests.rs": "fe1dd6ba26d7ebf588d4fe16b33d030303af5875d3e2017d30154125bfe84413", - "marreq-core/src/reqif/archive.rs": "0bb0e69455933ae03303ddcd53c50573aad11f176e603bdd32e1db28cb573d36", - "marreq-core/src/reqif/export.rs": "66cf5c01957bf4eba237b59c0cfb7863e9ae35d0648f3bd87a1668263a0234cd", - "marreq-core/src/reqif/import.rs": "0dbbdfac7dfe711840d1e83a38bec5b74334ef89da844928f9b0cc6207d6666e", - "marreq-core/src/reqif/mapping.rs": "1cb83d2f18e1c6161c05680d22ef16dfa055531519043247ff2685e8ea122189", - "marreq-core/src/reqif/mod.rs": "48a98c711cb01e94f40dd29a7b7f9abcf02d42e601f436e4961247c3aa4bba1a", - "marreq-core/src/reqif/schema.rs": "fc5601a2d63f35312efa437e19b14dfa197ab0bb0b4c38e731b52c0683ad0ff3", - "marreq-core/src/reqif/vendor_import_tests.rs": "bf74ae53531b3a6ad1f725e26be528b6f3332a00127c3e3edf924a3843bbaec4", - "marreq-core/src/rich_text.rs": "ff4e2c4f60e15f52e933fb6226cecf193f4bac9c47d45b1a0e2158d28751936e", - "marreq-core/src/routes/api_info.rs": "1994ccb506523391848cc2b5202bffffff5362c9f3e3937c833a4d07944f7467", - "marreq-core/src/routes/catchers.rs": "ed18bab7d2bc2bcfd36ca96a49fe1071acb08747d09cf010b01d53dc3db5e65e", - "marreq-core/src/routes/helpers.rs": "396527d619b65b32880b59d5f75b195372d2396164536d6b8224bbb78103a3ea", - "marreq-core/src/routes/mod.rs": "c25096024e4b6fd5c63ec7e0b658a34bb3faabad2b6a5c08825b37af1909c2a2", - "marreq-core/src/saved_view_definition.rs": "a222e3e907f7ed5dacc759961eba299389eae4bd79387b8370772b6a526da4c3", - "marreq-core/src/schema.rs": "4a8a5e2aa8a44ad974e1cb0737ebf609f80f0c729c103ec5476adb039f98f536", - "marreq-core/src/services/applicability_service.rs": "da755f23a963182b284686c1d4987356449e96764780646b33b80e804cf7e719", - "marreq-core/src/services/attachment_service.rs": "ba06b3dde76eca6730fe17db100b335d03daac762f953efffe8969af274b7338", - "marreq-core/src/services/base_service.rs": "e649209878f6920588beebca68188171e423bbadec50a5e8a3ead9d16ce0ad1c", - "marreq-core/src/services/baseline_service.rs": "726dbce5864593256f856cdbcdf4a3ce37faf627642cbb5968fb82438a767a07", - "marreq-core/src/services/cache_service.rs": "8a94395ef50b6b369cd935f3614e6aacdb24a4ad1e1237581214122afa9acb7a", - "marreq-core/src/services/category_service.rs": "9c5c14bfe03c398b2dde0d22aee7567c6dc5ef325c66d8598be7e46a6441b35c", - "marreq-core/src/services/comment_service.rs": "b7ead06f324840fea8c0ab35812b45c9a954d5a72c44fee340331c86b647db02", - "marreq-core/src/services/config_authorization_tests.rs": "b1ec6c9fbdf28769258f8d23c8aaaf1e4dbba62621c97e5b037b03ce1642d4ce", - "marreq-core/src/services/custom_field_service.rs": "7778d7e2a95560e608efe5ca4dd33353cf73d30a125f7eb7a679b38a24d295ec", - "marreq-core/src/services/decorated_requirement_service.rs": "e7a04e3beccbe151b1d936b53cbbd2df2d9a0405f649d6d02e868d6e842815b3", - "marreq-core/src/services/decorated_test_service.rs": "37c82e2843540b78367f7337988d347cedb03b25e7005edcb0b45972c07d4bd6", - "marreq-core/src/services/dsm_service.rs": "9409bdb79b7269305a04381cf77d6d8329de8fec54d033766d6cb344504331ed", - "marreq-core/src/services/email_sender.rs": "1a556590e3b034839fde6adc634dd563ca93a1642af070c5646b148690cf5a73", - "marreq-core/src/services/external_auth_service.rs": "1a03d40e81701a094f16b1010c1d90af5246d565f8ce35b5ecabb654a81b2dfe", - "marreq-core/src/services/group_service.rs": "bdb286fead51cde5d91a669f6f3aca77a1cabc86ef1389e287b3d193cebd3e90", - "marreq-core/src/services/log_service.rs": "b63aee03c8fde42e9143c2f4f19e7eb5df37a1299380829d093c0e44dcde5e5c", - "marreq-core/src/services/matrix_service.rs": "57ace88167fccefe005e4dabb852a44d4c333107308208f0bec6e7bfc82cd1bf", - "marreq-core/src/services/mentions.rs": "37338f744e659c1424409c3375e71fdc8df51c44a2ee67815f0b293383198483", - "marreq-core/src/services/mod.rs": "419b0ffb22da94c32ae983a7a1fd9cfe83cf8c6066d995d2afbebdab378f2af9", - "marreq-core/src/services/notification_service.rs": "959bf356384407505d9c13158072aea84767d32834b807c681667db6ab3bd575", - "marreq-core/src/services/project_service.rs": "cb0ba576c9e4a33700e99a07c2d57d9ed26f4be54f2c700a2c55380e402c8a6d", - "marreq-core/src/services/reqif_service.rs": "1c4134dbaeacb09b259507159528a593acaf36a0a182ec7ba6aa4aefbec92f24", - "marreq-core/src/services/reqifz_service.rs": "f838756002594afbedbc94346e1c143394b2e63c2fd1d071f3de49db972b2e31", - "marreq-core/src/services/requirement_analytics_service.rs": "1388c3f6b506802ff4fb9255e5047ad66b66701b2645750d460c647e287544f8", - "marreq-core/src/services/requirement_diff_service.rs": "33cd9c92855de59df501e8343b508e316275c03852703012967bfe74b53ea2d9", - "marreq-core/src/services/requirement_service.rs": "3e8d9dc89253780582981b3086094e66dfb69aeb329575ad810a68e32e57e084", - "marreq-core/src/services/saved_view_service.rs": "0c8376fdd9fc2d6eb402382a3338cac73f0507e1fb1ba1c64b305ec9a4bb778f", - "marreq-core/src/services/semantic_search/config.rs": "5203461fa16be63887069fb6ebf3ed0833096ee6823f050ffa73d93ca4300198", - "marreq-core/src/services/semantic_search/document_builder.rs": "81f6f8d224d5595eb6e57bc3604ecfc338bb634fca682cb5bc43e7d48332d52f", - "marreq-core/src/services/semantic_search/embedding_provider.rs": "3f8e5d3e66bf78ae3216cadc76ab4e4c7480226830abba4a2e722268fbd765a4", - "marreq-core/src/services/semantic_search/indexing_service.rs": "8ea2d41ab9a64a8958e78163ab76d1a960220a1f5786077f1c75cc6c69d62a08", - "marreq-core/src/services/semantic_search/llm_provider.rs": "b7318a4e6e8ae2e794a51a2d644d88cd6a6674b1de4fca7098d83cf85ffe4fa3", - "marreq-core/src/services/semantic_search/mod.rs": "2cf968e31b72125552698ad1beebd8648b2cc628410c3aff0046303470fd6200", - "marreq-core/src/services/semantic_search/search_service.rs": "3603273c894ddb0d2f2980ab8b6290f70664c3d313011ba0a082ea0f2a393c54", - "marreq-core/src/services/status_service.rs": "b85e510fc9deffa37c51106d4d56d033aba5f8499cd7a53fc94452708e081b23", - "marreq-core/src/services/tests.rs": "244cb904f099f380754efab68ed8632a350ed5c27b333670e7d057705004bcaa", - "marreq-core/src/services/use_case_service.rs": "0e1e3f42e63b5990389bc62ee9765dc3bd71d619908897d5ad036a52f85b60b4", - "marreq-core/src/services/user_provisioning_service.rs": "ed4a1d192b3a40e66f689f80730985cb9acb6e88bc6cb6307c875103257a42d9", - "marreq-core/src/services/user_service.rs": "855663cc391eda53582ca2bf8160bdbe5dfe6e803e257a5b377c7d2b6d2acb2d", - "marreq-core/src/services/verification_control_service.rs": "b2015f9051fdcbfabfec9330a890ba303feb55c252df6143c49a0cde4310fee7", - "marreq-core/src/services/verification_diff_service.rs": "63f1bdb62d3e1092bf45a5907fe3a393707e832f390bdc839dc7aae4c1804308", - "marreq-core/src/services/verification_method_service.rs": "8491d252d76cbf3f20fa20c2850e45d962ca2a55715247a8b25b781ae03d4a66", - "marreq-core/src/services/verification_service.rs": "75eb4a790eb92dd866e91037d6d4f8ccb3db80b61958014d5d2a3751e4ec10f2", - "marreq-core/src/status_enums.rs": "0b3794e640c934f10584780bb703cbd8dd54bf1fd218841c70e5975ee85e20a3", - "marreq-core/src/storage/content_type.rs": "e324695785b60c63fc14a76d53d4ab5a1891fb623ea6485502f92cb892f6c423", - "marreq-core/src/storage/local.rs": "112afb565a8eff8007ee499baaf5751a96dec78df5805b23d40effc8ce099801", - "marreq-core/src/storage/mod.rs": "79653093398c728e8a2be8270e0ccadcc14bf3c8ac8dbce62d0fe344c42de66b", - "marreq-core/src/use_case_document/diff.rs": "9059f723d9f14704f80e1901be32483d0c3de552dd61f8a45d5dcc12e5032dd9", - "marreq-core/src/use_case_document/mod.rs": "d27cff5c38cfc5a88a47b3e8615f7bf2ce19b11345b603dbd6e46fff125e72e7", - "marreq-core/src/use_case_document/operations.rs": "78ab1e3750a7ace36991660e23bc5fc9e05e88de2452d90ecbd4d7fa8095595c", - "marreq-core/src/validation/error.rs": "e80f3f225c3300a9e49f78c2a07ebbe66fb0ce7237e1010f8f51341380f0805d", - "marreq-core/src/validation/mod.rs": "41643b71b016e8a1d07384979ec9cd54c5ed860ab9a8a78326485251638f3cd0", - "marreq-core/src/validation/tests.rs": "589456901adce6f94a66db2b3ee33ea01f1db596a9be51680abdeef8b05fed1b", - "marreq-server/src/api/mod.rs": "5707e1cbc3c10b9e33e62bc7b3b244a7a565024a1c7147562634c0da7b1d3331", - "marreq-server/src/deployment.rs": "840d3a3324402d64a2448d888ea2f569656f2fc07c8d38dfc75b862b0e2714d9", - "marreq-server/src/main.rs": "b7c82e45b16873d46e9a4c178e1311d62b2d7a90a15d4e0d39a6848fe3c859b6", - "marreq-server/src/routes.rs": "348dbf2079a01db04f81209371c33c01e20ddab4ed4f1c8b34108e172c360058", - "mcp-server/compatibility.json": "0d9bcff76abe3a44b59c5bbcd3b73acd29266394e25c16b7c4b069bec28b9472", - "mcp-server/src/check-duplicates.ts": "ccdf3f47bb2d562a316b4d957cae8786f00c4255d1100e5f8bfb08202ae15d75", - "mcp-server/src/client.ts": "6011376ae4200afd83a8110d5c997df604791b3d058726b977e0270100c80306", - "mcp-server/src/context.ts": "7a0666e3f92ce56f55442425dbc1c917f1670665bd8c30fc363036d2479b09f4", - "mcp-server/src/credentials.ts": "e7697f096ccc60de4709e9b94c8bb0091c23fb090234ae4c2a36fc5f1011639c", - "mcp-server/src/gap-tools.ts": "c0453c6dac1ed6c6beb2460c47849ed5b6bf70e790b4ffc7b4fdc3f35b19f6a8", - "mcp-server/src/index.ts": "8ae7b012c087c4d522e688d1119b555d252ad2199553e8829b4445a73f46c272", - "mcp-server/src/pagination.ts": "64d0dcdec7dc6ba268f44745e281da0a2a52839804d328261be336686f8f5a5d", - "mcp-server/src/prompts.ts": "e80f45676aa1e29db25b1c637366a99c04bd0b7dd2aa402d050c000ea93ca698", - "mcp-server/src/remote.ts": "db6a0bee20bc73d3c6635d5364dc5a98e06a0b30aae5aa07482a664cef4d328b", - "mcp-server/src/resources.ts": "0c68424f0676391ecdf66df5ae8d78f026e4b82d8683e6fe0759ab33d6720fd6" -} diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index 4bb86309..9a62f0bc 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -213,179 +213,101 @@ A minimal **OpenAPI 3** sketch for auth and session project listing lives in [`o See [`../../docker/README.md`](../../docker/README.md). -## P0 contract-hardening audit and current coverage (#423) - -**This is an incomplete bootstrap, not completion of the P0 prerequisite.** The -OpenAPI inventory lists all 245 compiled HTTP API operations: 241 on Server and -245 on Cloud, including origin-root OAuth routes. Currently 39 operations have -explicit wire schemas; 206 are marked `x-marreq-unverified` and are inventory -entries only. The release completeness gate deliberately rejects them. A passing -OpenAPI validator or PR check does **not** establish full API compatibility. - -Architecture assessment: - -| Boundary | Implementation and observations | -| --- | --- | -| Shared backend | `marreq-core/src/app.rs::declared_mounts` composes policy-classified API and root OAuth routes. Rocket guards enforce sessions, Bearer scopes, membership and permissions. Diesel models are not automatically wire schemas: flattened DTOs, computed JSON, omitted fields and custom responders matter. | -| Server | `marreq-server/src/routes.rs` currently adds no endpoints. User creation/admin promotion and database backup are shared endpoints with deployment-mode restrictions, not absent Server/Cloud routes. | -| Cloud | Four additional public account endpoints: register, verify-email, forgot-password and reset-password. Their presence differs from Server; shared capabilities also differ at runtime. | -| Frontend | `frontend/src/api/*.ts`, plus direct fetches in pages/components. Session credentials and CSRF are essential; binary downloads, multipart imports and redirects cannot be modeled as ordinary JSON. Only metadata/provider discovery and selected use-case response models have migrated to generated types so far. | -| MCP | `mcp-server/src/client.ts` wraps project-scoped REST calls; `index.ts` defines tools, `remote.ts` owns MCP HTTP sessions and delegated credentials. Principal identity has nullable `client_id`/`grant_id`, even though the previous TypeScript definitions described optional strings/numbers. The Node MCP transport and `/healthz` are outside the Rust HTTP API. | -| Authentication | `/api/home/attention` now accepts `ProjectsRead` scoped credentials; the older session-only description above is stale. `/api/search` remains session-only. `/oauth/*` and `/.well-known/*` are mounted at the origin root. Guard/fairing responses can be empty or framework-generated; consumers must not assume every error is an `ApiError` JSON object. | -| Release | Existing component tags remain the publishing triggers. Independent artifact versions are allowed. Current `main` is never substituted for an unavailable historical contract. No deployment or global release migration is implemented here. | - -The explicitly modeled surface includes public build/deployment/CSRF/provider -metadata, current user, MCP principal, the four Cloud account routes, three OAuth -discovery routes, and all 25 use-case operations (including draft operations, -publishing, versions, approvals, comments, links, baseline content and diffs). -Use-case document requests distinguish omitted defaults from response properties -that Serde always emits. Unmodeled inventory entries do not promise fabricated -response bodies/status codes. Origin-root UI/HTML and internal CSRF-denial routes -are excluded from the API inventory comparison. - -### Source of truth and change workflow - -`openapi.yaml` is the wire contract. This retains Rocket/Diesel and introduces no -backend runtime dependency. `api-contract-routes.json` and both -`generated-contract.ts` files are reproducible derivatives, not independently -maintained contracts. The source-review digest records which producer/consumer -sources were audited. It detects changes requiring review, but **does not prove -serialization correctness** and is not a replacement for integration tests. - -When changing an endpoint: - -1. Inspect the actual Rocket mount, guards, Serde serialization/default/flatten/ - skip behavior, response status, and both consumers. Update OpenAPI accordingly. -2. Add or extend producer exchanges and consumer tests. Remove - `x-marreq-unverified` only after auditing the complete operation, not merely - because its route exists. Preserve unknown error-body handling. -3. Regenerate derived artifacts and explicitly record the source review: - - ```sh - python3 scripts/contracts/generate.py - python3 scripts/contracts/check.py --record-source-review - python3 scripts/contracts/check.py - python3 -m unittest discover -s scripts/contracts -v - ``` - -4. Run the compiled route check and isolated real-backend checks: - - ```sh - cargo test --locked -p marreq-cloud --test api_contract_inventory - npm ci --prefix frontend - npm ci --prefix mcp-server - scripts/contracts/test-backend.sh - ``` - -Install Python tooling with `python3 -m pip install -r -scripts/contracts/requirements.txt` in a virtual environment. Backend builds need -`libpq-dev`; Docker and Node 22 are needed for the isolated smoke check. The script -creates a new loopback-only PostgreSQL container with tmpfs storage, applies -migrations, seeds synthetic OAuth credentials through the integration test, and -starts a real `marreq-server` process. It exercises the actual frontend transport -and built MCP client over HTTP. It records requests/responses, never Authorization -headers or cookies. It ignores existing database URLs when creating its test DB; -it never connects to a production database. Cleanup runs on exit. Missing/skipped -critical producer exchanges fail transcript validation. - -### Remaining risks before P0 can be accepted - -- Audit the remaining **206** operations: authentication protocol/session flows, - projects/groups, requirements/verifications, traceability, matrices, baselines, - catalogs, administration, search, imports, exports, reports and attachments. - Presence in the inventory is not schema coverage. -- Complete per-consumer operation selection and generated request/response type - adoption, including direct frontend fetches. Bundles currently conservatively - require the entire backend surface; this can reject unrelated changes. -- Add exhaustive consumer request checks and live Cloud/session/OAuth protocol - coverage. Current executable producer/consumer coverage is representative, - not exhaustive, and no historical release is retrospectively certified. -- Retain verified bundles for real released artifact versions and bootstrap an - accurate deployment plan. Versions in package manifests alone are insufficient. -- Close existing test/security failures before making this a release-ready change. - -Until these are resolved, leave the PR in draft and keep publication blocked by -`check.py --require-complete`. Do not remove that gate to make a release green. - -### Implementation validation record (2026-10-27) - -All npm installs used the committed lockfiles (`npm ci` at root, in `frontend`, -and in `mcp-server`). Rust commands on the development host used temporary -`LIBRARY_PATH`/`LD_LIBRARY_PATH` settings pointing to an extracted `libpq` package; -no system/production database configuration was changed. - -| Exact command | Result | -| --- | --- | -| `python3 scripts/contracts/check.py` | PASS; 245 inventoried operations, 39 modeled operations, 206 explicit gaps | -| `python3 scripts/contracts/generate.py --check` | PASS; generated types and compiled-route expectations reproducible | -| `python3 -m unittest discover -s scripts/contracts -v` | PASS; 39 positive/negative tests, including historical plans and process exit codes | -| `python3 scripts/contracts/check.py --require-complete` | EXPECTED FAIL (exit 1); incomplete producer contract plus 206 unverified operations | -| `python3 scripts/contracts/compatibility.py --manifest /tmp/marreq-nonexistent-plan.json` | EXPECTED FAIL (exit 1); structured missing-evidence error | -| `cargo test --locked -p marreq-cloud --test api_contract_inventory` | PASS; compiled Server and Cloud routes match derived expectations | -| `scripts/contracts/test-backend.sh` | PASS; disposable migrated PostgreSQL, production Rocket handlers, real Server HTTP process and actual frontend/MCP clients; 15 validated exchanges, 33 explicitly inventory-only exchanges, three corrupted-traffic negative controls rejected | -| `cargo test --workspace` | PASS; 2,061 passed, one ignored. Default workspace feature unification enables test helpers; DB-specific tests without explicit test DB may skip internally. The separate isolated production-path run above is required evidence. | -| `cargo fmt --all -- --check` | PASS | -| `cargo clippy --workspace --all-targets -- -D warnings` | PASS | -| `npm run build --prefix frontend` | PASS; existing large-chunk warning | -| `npm run build --prefix mcp-server` | PASS | -| `npm test --prefix mcp-server` | PASS; 51 tests | -| `npm run test:coverage --prefix frontend` | PASS; 108 suites, 704 tests, 70.36% line coverage. An earlier ordinary full run had one report-builder failure; its isolated rerun and full coverage rerun passed. | -| `npx vitest run marreq-core/tests/js` | PASS; 411 tests, seven skipped | -| `npx --no-install stylelint 'frontend/static/**/*.css' --config .stylelintrc.json` | PASS | -| `npm run check:unused-css` | PASS (exit 0); existing warning reports 379 unused selectors | -| `npm audit --prefix frontend --omit=dev --audit-level=high` | PASS; no production vulnerabilities reported | -| `npm audit --prefix mcp-server --omit=dev --audit-level=high` | FAIL; existing lockfile reports one high and three moderate advisories | -| `cargo deny check advisories` | FAIL; existing locked dependencies trigger RUSTSEC-2026-0384, RUSTSEC-2026-0385 and RUSTSEC-2026-0387, plus yanked-crate warnings | -| `bash -n scripts/check-api-compatibility.sh scripts/contracts/test-backend.sh` | PASS | -| `git diff --check` | PASS | - -Dependency/security failures are reported, not suppressed or allowlisted. Their -remediation remains necessary before release readiness. The source-review digest -is deliberately conservative: backend service/model/guard changes and direct -frontend calls outside API modules require renewed review as well. This does not -replace the remaining serialization/consumer verification work. - -Changed files in this draft implementation (including generated artifacts): - -- `.github/workflows/api-contracts.yml` -- `.github/workflows/marreq-ci.yml` -- `.github/workflows/release-artifacts.yml` -- `.gitignore` -- `Cargo.lock` -- `docker/Dockerfile` -- `docker/frontend/Dockerfile` -- `docs/developer/api-contract-routes.json` -- `docs/developer/api-contract-source-review.json` -- `docs/developer/http-api-contract.md` -- `docs/developer/mcp-setup.md` -- `docs/developer/openapi.yaml` -- `docs/developer/use-cases-api.md` -- `docs/releases.md` -- `frontend/src/api/generated-contract.ts` -- `frontend/src/api/types.ts` -- `frontend/src/hooks/__tests__/useBuildInfo.test.ts` -- `frontend/src/pages/__tests__/AccountPage.test.tsx` -- `frontend/src/pages/__tests__/AdminPage.test.tsx` -- `frontend/src/pages/__tests__/HelpPage.test.tsx` -- `frontend/src/pages/__tests__/LoginPage.test.tsx` -- `marreq-cloud/Cargo.toml` -- `marreq-cloud/tests/api_contract_inventory.rs` -- `marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs` -- `mcp-server/Dockerfile` -- `mcp-server/compatibility.json` -- `mcp-server/src/client.ts` -- `mcp-server/src/generated-contract.ts` -- `mcp-server/src/remote.ts` -- `scripts/check-api-compatibility.sh` -- `scripts/contracts/bundle.py` -- `scripts/contracts/check.py` -- `scripts/contracts/check_baseline.py` -- `scripts/contracts/compatibility.py` -- `scripts/contracts/generate.py` -- `scripts/contracts/inventory.py` -- `scripts/contracts/requirements.txt` -- `scripts/contracts/smoke-consumers.mjs` -- `scripts/contracts/test-backend.sh` -- `scripts/contracts/test_compatibility.py` -- `scripts/contracts/test_contract_tooling.py` -- `scripts/contracts/verify_transcript.py` +## Consumer-aware contract verification (#423 / #424) + +`docs/developer/openapi.yaml` is the single wire contract. Only operations used by +frontend or MCP belong in release compatibility checks. A route inventory is not +serialization evidence: `x-marreq-unverified` means the operation still needs an +audit and cannot be used in a releasable consumer bundle. Unconsumed backend +endpoints are outside this contract; adding/changing them does not itself block +an existing consumer. + +**The refactor remains draft.** The earlier 245-operation global completeness gate +was removed, but most remaining gaps are genuinely consumed APIs. Completing their +wire schemas, binding more producer DTO serialization to those schemas, and +retaining actual historical artifact evidence are still release blockers. A +passing document validator alone is not a compatibility guarantee. + +### Architecture and rationale + +- `tools/marreq-contracts` is a workspace Rust CLI/library. Serde/serde_yaml_ng parse + data, `semver` validates versions, `sha2` pins bundle content, and `jsonschema` + validates actual request/response bodies offline. +- OpenAPI validation and breaking-change analysis use checksum-pinned **oasdiff + 1.34.0**, not a custom JSON Schema subset algorithm. See the upstream + [breaking-change semantics](https://github.com/oasdiff/oasdiff/blob/main/docs/BREAKING-CHANGES.md). + Request acceptance and response guarantees are compared in their respective + directions. Unknown advanced schema semantics fail for the affected selected + schema; they never silently certify a combination. +- Consumer calls are parsed with the established TypeScript/TSX **tree-sitter** + grammar. The analyzer follows finite URL helpers, local constants, templates, + branch alternatives and explicit HTTP methods. It includes awaited generic + calls, direct fetches, shared transports, multipart/download wrappers, and API + browser navigation. Unknown paths/methods fail; there is no source regex parser, + source-review checksum or separately maintained full route list. +- The small protocol dependency list covers browser/OAuth callbacks and discovery + which are not ordinary `fetch` expressions. Its IDs must exist in OpenAPI. All + other dependencies are discovered from consumer source. The only dynamic href + exclusion is `StatementText`'s sanitized user-content link: user-authored + navigation is not an application HTTP client operation. It does not exclude + fetches or fixed API links in that component. +- Compiled Rocket mount tests check every declared consumer operation against + Server/Cloud registration. Extra backend routes are permitted. Four public + account routes are Cloud-only; shared deployment capabilities remain runtime + behavior rather than invented Server/Cloud route differences. +- Standard `openapi-typescript` emits only schemas reachable by each consumer. + Committed declarations keep Rust/global OpenAPI out of frontend/MCP Docker + builds. Each consumer owns its minimal build input; the duplicate global type + snapshots were replaced by independently selected declarations. + +The old Python comparator, regex inventory parser, source-review hashes, +generated route JSON and separate reusable workflow were removed. The unrelated +existing requirements-compliance Python script is retained. + +### Developer commands + +Install the pinned upstream engine once (`scripts/install-oasdiff.sh`; CI does +this automatically), add its destination to PATH, or set `OASDIFF` to that binary. +The installer currently handles Linux x86_64; other platforms may install the same +upstream version themselves. No contract command downloads refs or schemas. + +```sh +cargo test --locked -p marreq-contracts +cargo run --locked -p marreq-contracts -- validate +cargo run --locked -p marreq-contracts -- coverage --component frontend +cargo run --locked -p marreq-contracts -- coverage --component mcp +node scripts/generate-api-types.mjs +node scripts/generate-api-types.mjs --check +cargo test --locked -p marreq-cloud --test api_contract_inventory +scripts/test-api-contracts.sh +``` + +`validate` checks document/policy validity and requires a verified schema for +**every discovered consumer dependency**. `--document-only` checks the document +without certifying consumer coverage; it cannot create a consumer release bundle. +`select --allow-unverified` is used only for development type generation, never +for bundle export or compatibility verification. + +When adding/changing an HTTP call, update the actual Rocket implementation and +OpenAPI, then regenerate declarations and extend existing consumer/producer tests. +A new unknown call, unresolved dynamic call, or newly used unverified operation +fails validation. Remove `x-marreq-unverified` only after auditing request defaults, +response nullability/required fields, errors/statuses, media, guards and scopes. +Do not infer a schema from one response or from a TypeScript assertion. + +The isolated smoke test runs migrated tmpfs PostgreSQL plus real Server and Cloud +binaries, exercising the actual frontend transport/session flow and MCP HTTP +client. Test processes use a clean directory/environment, loopback addresses and +synthetic credentials; existing `.env`/database/SMTP configuration is excluded. +Transcripts omit headers/cookies and redact login credentials. Default workspace +unit tests can unify `test-helpers`; the separate production-path run is essential. +It remains representative evidence, not certification of all unmodeled APIs. + +### Scope limits + +The CLI reports exact operation and call-site counts. Keep the PR draft while any +required operation is unverified, a producer behavior lacks adequate contract +protection, or a required check fails. Hand-maintained schemas still need broader +Rust DTO/serialization verification; compiled route presence alone is insufficient. +The remaining upstream Rust advisory is tracked in +[#425](https://github.com/MarreqSW/Marreq/issues/425); the security gate is unchanged. diff --git a/docs/developer/mcp-setup.md b/docs/developer/mcp-setup.md index 1cc120ba..7b85eae6 100644 --- a/docs/developer/mcp-setup.md +++ b/docs/developer/mcp-setup.md @@ -360,14 +360,13 @@ Reference: shared route list in `marreq-core/src/api/mod.rs` (plus deployment-sp - **Connection refused**: Ensure Marreq is running and `MARREQ_BASE_URL` is correct (e.g. `http://localhost:8000`). - **Tool not found**: Confirm `MARREQ_MODE` and `MARREQ_TRACE_WRITE` โ€” extended and trace tools are only registered when those settings enable them. -## Backend compatibility metadata - -`mcp-server/compatibility.json` records the build-time supported backend range, -independently of the MCP component's version. It is retained in the MCP image. -CI validates its shape and release verification combines it with the exact -released MCP contract bundle; ranges alone do not establish compatibility. -See [release contract verification](../releases.md#api-compatibility-gate-and-retained-artifact-contracts-423-p0). -The current bootstrap still has unverified API operations and intentionally cannot -certify an independently deployed production combination. Existing mocked MCP -unit tests remain; `scripts/contracts/test-backend.sh` additionally executes the -actual client against a real loopback Rocket server and an isolated database. +## Artifact compatibility + +`mcp-server/compatibility.json` declares its backend range independently of MCP's +version and remains in the MCP image. The complete global OpenAPI is not copied +into that image or the public nginx directory. Consumer-specific immutable bundles +are retained alongside release artifacts and verified by the Rust +`marreq-contracts` CLI; see [release verification](../releases.md#exact-artifact-api-compatibility-423). +A missing historical bundle or an unknown operation fails safely. Backend ranges +alone are not compatibility evidence. The existing mocked tool tests remain, +with actual HTTP/session/OAuth smoke checks in `scripts/test-api-contracts.sh`. diff --git a/docs/developer/openapi.yaml b/docs/developer/openapi.yaml index 932e8797..6f23216b 100644 --- a/docs/developer/openapi.yaml +++ b/docs/developer/openapi.yaml @@ -1,20 +1,13 @@ openapi: 3.1.0 -info: {title: Marreq HTTP API, version: 0.1.0, description: Rocket route inventory plus explicitly audited wire - schemas. Operations marked x-marreq-unverified are NOT complete contracts and cause release verification to - fail closed. Root OAuth paths are distinct from /api. The Node MCP transport is not a Rust REST endpoint. See - http-api-contract.md for scope and remaining work.} +info: {title: Marreq HTTP API, version: 0.1.0, description: Consumer-facing HTTP contracts. Unconsumed backend routes + are outside compatibility enforcement. Required operations marked x-marreq-unverified fail verification; no + missing schemas are inferred from inventory entries.} servers: - {url: /} paths: /api/projects/{project_id}/requirements/{id}/activity: get: operationId: activity_requirement_activity_by_project - tags: [activity] - summary: activity / requirement activity by project - x-marreq-variants: [server, cloud] - x-marreq-handler: activity::requirement_activity_by_project - x-marreq-source: marreq-core/src/api/activity.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -27,20 +20,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verifications/{id}/activity: get: operationId: activity_verification_activity_by_project - tags: [activity] - summary: activity / verification activity by project - x-marreq-variants: [server, cloud] - x-marreq-handler: activity::verification_activity_by_project - x-marreq-source: marreq-core/src/api/activity.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -53,73 +39,32 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/applicability: get: operationId: applicability_list - tags: [applicability] - summary: applicability / list - x-marreq-variants: [server, cloud] - x-marreq-handler: applicability::list - x-marreq-source: marreq-core/src/api/applicability.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: applicability_create - tags: [applicability] - summary: applicability / create - x-marreq-variants: [server, cloud] - x-marreq-handler: applicability::create - x-marreq-source: marreq-core/src/api/applicability.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/applicability/{id}: - get: - operationId: applicability_get - tags: [applicability] - summary: applicability / get - x-marreq-variants: [server, cloud] - x-marreq-handler: applicability::get - x-marreq-source: marreq-core/src/api/applicability.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] put: operationId: applicability_update - tags: [applicability] - summary: applicability / update - x-marreq-variants: [server, cloud] - x-marreq-handler: applicability::update - x-marreq-source: marreq-core/src/api/applicability.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -128,18 +73,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: applicability_delete - tags: [applicability] - summary: applicability / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: applicability::delete - x-marreq-source: marreq-core/src/api/applicability.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -148,19 +86,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/attachments: get: operationId: attachments_list - tags: [attachments] - summary: attachments / list - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::list - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectRequirementsAndVerificationsRead] parameters: - in: path name: project_id @@ -169,19 +100,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: attachments_upload - tags: [attachments] - summary: attachments / upload - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::upload - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -190,46 +114,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/attachments/{attachment_id}/download: get: operationId: attachments_download - tags: [attachments] - summary: attachments / download - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::download - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectAccessOrBearer] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: attachment_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/attachments/{attachment_id}: - delete: - operationId: attachments_delete - tags: [attachments] - summary: attachments / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::delete - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -242,20 +133,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/storage: get: operationId: attachments_get_storage - tags: [attachments] - summary: attachments / get storage - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::get_storage - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -264,20 +148,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/storage/quota: put: operationId: attachments_set_quota - tags: [attachments] - summary: attachments / set quota - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::set_quota - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [AdminOnly] parameters: - in: path name: project_id @@ -286,19 +163,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/attachments: get: operationId: attachments_list_for_baseline - tags: [attachments] - summary: attachments / list for baseline - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::list_for_baseline - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -311,20 +181,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: get: operationId: attachments_download_for_baseline - tags: [attachments] - summary: attachments / download for baseline - x-marreq-variants: [server, cloud] - x-marreq-handler: attachments::download_for_baseline - x-marreq-source: marreq-core/src/api/attachments.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -341,20 +204,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/auth/providers: get: operationId: auth_auth_providers - tags: [auth] - summary: auth / auth providers - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_providers - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [] security: [] parameters: [] responses: @@ -366,12 +222,6 @@ paths: /api/auth/external/{provider}/start: get: operationId: auth_auth_external_start - tags: [auth] - summary: auth / auth external start - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_external_start - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [] parameters: - in: path name: provider @@ -380,39 +230,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: [] - /api/auth/external/{provider}/link: - post: - operationId: auth_auth_external_link_start - tags: [auth] - summary: auth / auth external link start - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_external_link_start - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] - parameters: - - in: path - name: provider - required: true - schema: {type: string} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/auth/external/{provider}/callback: get: operationId: auth_auth_external_callback - tags: [auth] - summary: auth / auth external callback - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_external_callback - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] parameters: - in: path name: provider @@ -421,128 +243,21 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: [] - /api/auth/csrf: - get: - operationId: auth_auth_csrf - tags: [auth] - summary: auth / auth csrf - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_csrf - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [] - security: [] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - csrf_token: &id005 {type: string} - required: [csrf_token] - additionalProperties: true - /api/auth/login: - post: - operationId: auth_auth_login - tags: [auth] - summary: auth / auth login - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_login - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: [] /api/auth/identities: get: operationId: auth_auth_identities - tags: [auth] - summary: auth / auth identities - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_identities - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/auth/identities/{identity_id}: - delete: - operationId: auth_auth_identity_delete - tags: [auth] - summary: auth / auth identity delete - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_identity_delete - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] - parameters: - - in: path - name: identity_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/auth/logout: - post: - operationId: auth_auth_logout - tags: [auth] - summary: auth / auth logout - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_logout - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/auth/change-password: - post: - operationId: auth_auth_change_password - tags: [auth] - summary: auth / auth change password - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_change_password - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/auth/me: get: operationId: auth_auth_me - tags: [auth] - summary: auth / auth me - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_me - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] security: - cookieAuth: [] parameters: [] @@ -561,63 +276,36 @@ paths: schema: {$ref: '#/components/schemas/ApiError'} put: operationId: auth_auth_update_me - tags: [auth] - summary: auth / auth update me - x-marreq-variants: [server, cloud] - x-marreq-handler: auth::auth_update_me - x-marreq-source: marreq-core/src/api/auth.rs - x-marreq-guards: [OptionalSessionUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/admin/backup/info: get: operationId: backup_info - tags: [backup] - summary: backup / info - x-marreq-variants: [server, cloud] - x-marreq-handler: backup::info - x-marreq-source: marreq-core/src/api/backup.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/admin/backup: post: operationId: backup_create - tags: [backup] - summary: backup / create - x-marreq-variants: [server, cloud] - x-marreq-handler: backup::create - x-marreq-source: marreq-core/src/api/backup.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/baselines: get: operationId: baselines_list - tags: [baselines] - summary: baselines / list - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::list - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -626,19 +314,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: baselines_create - tags: [baselines] - summary: baselines / create - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::create - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesWrite] parameters: - in: path name: project_id @@ -647,20 +328,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}: get: operationId: baselines_get - tags: [baselines] - summary: baselines / get - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::get - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -673,20 +347,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/requirements: get: operationId: baselines_get_requirements - tags: [baselines] - summary: baselines / get requirements - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::get_requirements - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -699,20 +366,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/traceability: get: operationId: baselines_get_traceability - tags: [baselines] - summary: baselines / get traceability - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::get_traceability - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -725,20 +385,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/verifications: get: operationId: baselines_get_verifications - tags: [baselines] - summary: baselines / get verifications - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::get_verifications - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -751,20 +404,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/diff: get: operationId: baselines_diff_baselines - tags: [baselines] - summary: baselines / diff baselines - x-marreq-variants: [server, cloud] - x-marreq-handler: baselines::diff_baselines - x-marreq-source: marreq-core/src/api/baselines.rs - x-marreq-guards: [ProjectBaselinesRead] parameters: - in: path name: project_id @@ -773,138 +419,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/cache/stats: - get: - operationId: cache_stats - tags: [cache] - summary: cache / stats - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::stats - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/clear: - post: - operationId: cache_clear - tags: [cache] - summary: cache / clear - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::clear - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/cleanup: - post: - operationId: cache_cleanup - tags: [cache] - summary: cache / cleanup - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::cleanup - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/performance: - get: - operationId: cache_performance - tags: [cache] - summary: cache / performance - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::performance - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/recommendations: - get: - operationId: cache_recommendations - tags: [cache] - summary: cache / recommendations - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::recommendations - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/reset-counters: - post: - operationId: cache_reset_counters - tags: [cache] - summary: cache / reset counters - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::reset_counters - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/cache/health: - get: - operationId: cache_health - tags: [cache] - summary: cache / health - x-marreq-variants: [server, cloud] - x-marreq-handler: cache::health - x-marreq-source: marreq-core/src/api/cache.rs - x-marreq-guards: [] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: [] /api/projects/{project_id}/catalog: get: operationId: catalog_get - tags: [catalog] - summary: catalog / get - x-marreq-variants: [server, cloud] - x-marreq-handler: catalog::get - x-marreq-source: marreq-core/src/api/catalog.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -913,36 +434,22 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/categories: get: operationId: categories_list - tags: [categories] - summary: categories / list - x-marreq-variants: [server, cloud] - x-marreq-handler: categories::list - x-marreq-source: marreq-core/src/api/categories.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: categories_create - tags: [categories] - summary: categories / create - x-marreq-variants: [server, cloud] - x-marreq-handler: categories::create - x-marreq-source: marreq-core/src/api/categories.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} @@ -952,34 +459,8 @@ paths: security: - cookieAuth: [] /api/categories/{id}: - get: - operationId: categories_get - tags: [categories] - summary: categories / get - x-marreq-variants: [server, cloud] - x-marreq-handler: categories::get - x-marreq-source: marreq-core/src/api/categories.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] put: operationId: categories_update - tags: [categories] - summary: categories / update - x-marreq-variants: [server, cloud] - x-marreq-handler: categories::update - x-marreq-source: marreq-core/src/api/categories.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -988,18 +469,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: categories_delete - tags: [categories] - summary: categories / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: categories::delete - x-marreq-source: marreq-core/src/api/categories.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -1008,19 +482,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/requirements/{requirement_id}/comments: get: operationId: comments_list_by_project - tags: [comments] - summary: comments / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: comments::list_by_project - x-marreq-source: marreq-core/src/api/comments.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -1033,19 +500,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: comments_create_by_project - tags: [comments] - summary: comments / create by project - x-marreq-variants: [server, cloud] - x-marreq-handler: comments::create_by_project - x-marreq-source: marreq-core/src/api/comments.rs - x-marreq-guards: [ProjectRequirementsWrite] parameters: - in: path name: project_id @@ -1058,20 +518,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/requirements/{requirement_id}/comments: get: operationId: comments_list - tags: [comments] - summary: comments / list - x-marreq-variants: [server, cloud] - x-marreq-handler: comments::list - x-marreq-source: marreq-core/src/api/comments.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: requirement_id @@ -1080,18 +533,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: comments_create - tags: [comments] - summary: comments / create - x-marreq-variants: [server, cloud] - x-marreq-handler: comments::create - x-marreq-source: marreq-core/src/api/comments.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: requirement_id @@ -1100,19 +546,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/custom_fields: get: operationId: custom_fields_list_by_project - tags: [custom_fields] - summary: custom fields / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: custom_fields::list_by_project - x-marreq-source: marreq-core/src/api/custom_fields.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1121,19 +560,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: custom_fields_create - tags: [custom_fields] - summary: custom fields / create - x-marreq-variants: [server, cloud] - x-marreq-handler: custom_fields::create - x-marreq-source: marreq-core/src/api/custom_fields.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1148,39 +580,8 @@ paths: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/custom_fields/{field_id}: - get: - operationId: custom_fields_get - tags: [custom_fields] - summary: custom fields / get - x-marreq-variants: [server, cloud] - x-marreq-handler: custom_fields::get - x-marreq-source: marreq-core/src/api/custom_fields.rs - x-marreq-guards: [ProjectAccessOrBearer] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: field_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] put: operationId: custom_fields_update - tags: [custom_fields] - summary: custom fields / update - x-marreq-variants: [server, cloud] - x-marreq-handler: custom_fields::update - x-marreq-source: marreq-core/src/api/custom_fields.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1193,19 +594,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: custom_fields_delete - tags: [custom_fields] - summary: custom fields / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: custom_fields::delete - x-marreq-source: marreq-core/src/api/custom_fields.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1218,37 +612,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/dashboard: - get: - operationId: dashboard_dashboard_json - tags: [dashboard] - summary: dashboard / dashboard json - x-marreq-variants: [server, cloud] - x-marreq-handler: dashboard::dashboard_json - x-marreq-source: marreq-core/src/api/dashboard.rs - x-marreq-guards: [OptionalSessionUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/dsm: get: operationId: dsm_get_dsm - tags: [dsm] - summary: dsm / get dsm - x-marreq-variants: [server, cloud] - x-marreq-handler: dsm::get_dsm - x-marreq-source: marreq-core/src/api/dsm.rs - x-marreq-guards: [ProjectRequirementsAndTraceabilityRead] parameters: - in: path name: project_id @@ -1257,20 +627,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/requirements.xlsx: get: operationId: exports_export_requirements_xlsx - tags: [exports] - summary: exports / export requirements xlsx - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_requirements_xlsx - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1279,20 +642,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/verifications.xlsx: get: operationId: exports_export_verifications_xlsx - tags: [exports] - summary: exports / export verifications xlsx - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_verifications_xlsx - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1301,20 +657,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/matrix.xlsx: get: operationId: exports_export_matrix_xlsx - tags: [exports] - summary: exports / export matrix xlsx - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_matrix_xlsx - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1323,20 +672,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/dsm.xlsx: get: operationId: exports_export_dsm_xlsx - tags: [exports] - summary: exports / export dsm xlsx - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_dsm_xlsx - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1345,20 +687,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/matrix-links.xlsx: get: operationId: exports_export_matrix_links_xlsx - tags: [exports] - summary: exports / export matrix links xlsx - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_matrix_links_xlsx - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1367,20 +702,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/requirements.pdf: get: operationId: exports_export_requirements_pdf - tags: [exports] - summary: exports / export requirements pdf - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_requirements_pdf - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1389,20 +717,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/report.pdf: get: operationId: exports_export_report_pdf - tags: [exports] - summary: exports / export report pdf - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_report_pdf - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1411,20 +732,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/requirements.reqif: get: operationId: exports_export_requirements_reqif - tags: [exports] - summary: exports / export requirements reqif - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_requirements_reqif - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1433,20 +747,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/requirements.reqifz: get: operationId: exports_export_requirements_reqifz - tags: [exports] - summary: exports / export requirements reqifz - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_requirements_reqifz - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1455,20 +762,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/baselines/{filename}: get: operationId: exports_export_baseline_reqif - tags: [exports] - summary: exports / export baseline reqif - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_baseline_reqif - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1481,20 +781,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/bundle.json: get: operationId: exports_export_project_bundle - tags: [exports] - summary: exports / export project bundle - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_project_bundle - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1503,20 +796,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/exports/bundle.zip: get: operationId: exports_export_project_bundle_zip - tags: [exports] - summary: exports / export project bundle zip - x-marreq-variants: [server, cloud] - x-marreq-handler: exports::export_project_bundle_zip - x-marreq-source: marreq-core/src/api/exports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1525,73 +811,45 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups: get: operationId: groups_list - tags: [groups] - summary: groups / list - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::list - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: groups_create - tags: [groups] - summary: groups / create - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::create - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups/creatable: get: operationId: groups_list_creatable - tags: [groups] - summary: groups / list creatable - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::list_creatable - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups/{group_id}: get: operationId: groups_get - tags: [groups] - summary: groups / get - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::get - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1600,19 +858,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] patch: operationId: groups_update - tags: [groups] - summary: groups / update - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::update - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1621,19 +872,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: groups_delete - tags: [groups] - summary: groups / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::delete - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1642,20 +886,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups/{group_id}/projects: get: operationId: groups_list_projects - tags: [groups] - summary: groups / list projects - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::list_projects - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1664,20 +901,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups/{group_id}/members: get: operationId: groups_list_members - tags: [groups] - summary: groups / list members - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::list_members - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1686,20 +916,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/groups/{group_id}/members/{user_id}: put: operationId: groups_set_member_role - tags: [groups] - summary: groups / set member role - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::set_member_role - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1712,19 +935,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: groups_remove_member - tags: [groups] - summary: groups / remove member - x-marreq-variants: [server, cloud] - x-marreq-handler: groups::remove_member - x-marreq-source: marreq-core/src/api/groups.rs - x-marreq-guards: [ApiUserOrBearer] parameters: - in: path name: group_id @@ -1737,55 +953,34 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/home/attention: get: operationId: home_attention - tags: [home] - summary: home / attention - x-marreq-variants: [server, cloud] - x-marreq-handler: home::attention - x-marreq-source: marreq-core/src/api/home.rs - x-marreq-guards: [ProjectsRead] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/search: get: operationId: home_search - tags: [home] - summary: home / search - x-marreq-variants: [server, cloud] - x-marreq-handler: home::search - x-marreq-source: marreq-core/src/api/home.rs - x-marreq-guards: [SessionUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/imports/excel/preview: post: operationId: imports_preview_excel - tags: [imports] - summary: imports / preview excel - x-marreq-variants: [server, cloud] - x-marreq-handler: imports::preview_excel - x-marreq-source: marreq-core/src/api/imports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1794,20 +989,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/imports/excel: post: operationId: imports_commit_excel - tags: [imports] - summary: imports / commit excel - x-marreq-variants: [server, cloud] - x-marreq-handler: imports::commit_excel - x-marreq-source: marreq-core/src/api/imports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1816,20 +1004,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/imports/reqif: post: operationId: imports_commit_reqif - tags: [imports] - summary: imports / commit reqif - x-marreq-variants: [server, cloud] - x-marreq-handler: imports::commit_reqif - x-marreq-source: marreq-core/src/api/imports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -1838,123 +1019,64 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/imports/bundle: post: operationId: imports_import_project_bundle - tags: [imports] - summary: imports / import project bundle - x-marreq-variants: [server, cloud] - x-marreq-handler: imports::import_project_bundle - x-marreq-source: marreq-core/src/api/imports.rs - x-marreq-guards: [ApiUserOrBearer] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/admin/logs: get: operationId: logs_list - tags: [logs] - summary: logs / list - x-marreq-variants: [server, cloud] - x-marreq-handler: logs::list - x-marreq-source: marreq-core/src/api/logs.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/admin/logs/stats: get: operationId: logs_stats - tags: [logs] - summary: logs / stats - x-marreq-variants: [server, cloud] - x-marreq-handler: logs::stats - x-marreq-source: marreq-core/src/api/logs.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/admin/logs/export.json: get: operationId: logs_export_json - tags: [logs] - summary: logs / export json - x-marreq-variants: [server, cloud] - x-marreq-handler: logs::export_json - x-marreq-source: marreq-core/src/api/logs.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/admin/logs/cleanup: post: operationId: logs_cleanup - tags: [logs] - summary: logs / cleanup - x-marreq-variants: [server, cloud] - x-marreq-handler: logs::cleanup - x-marreq-source: marreq-core/src/api/logs.rs - x-marreq-guards: [AdminOnly] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/matrix: - get: - operationId: matrix_list - tags: [matrix] - summary: matrix / list - x-marreq-variants: [server, cloud] - x-marreq-handler: matrix::list - x-marreq-source: marreq-core/src/api/matrix.rs - x-marreq-guards: [ApiUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/matrix: get: operationId: matrix_list_by_project - tags: [matrix] - summary: matrix / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: matrix::list_by_project - x-marreq-source: marreq-core/src/api/matrix.rs - x-marreq-guards: [ProjectTraceabilityRead] parameters: - in: path name: project_id @@ -1963,20 +1085,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verifications/{verification_id}/matrix: get: operationId: matrix_get_verification_matrix - tags: [matrix] - summary: matrix / get verification matrix - x-marreq-variants: [server, cloud] - x-marreq-handler: matrix::get_verification_matrix - x-marreq-source: marreq-core/src/api/matrix.rs - x-marreq-guards: [ProjectTraceabilityRead] parameters: - in: path name: project_id @@ -1989,19 +1104,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] put: operationId: matrix_put_verification_matrix - tags: [matrix] - summary: matrix / put verification matrix - x-marreq-variants: [server, cloud] - x-marreq-handler: matrix::put_verification_matrix - x-marreq-source: marreq-core/src/api/matrix.rs - x-marreq-guards: [ProjectTraceabilityWrite] parameters: - in: path name: project_id @@ -2014,20 +1122,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/mcp/principal: get: operationId: mcp_principal - tags: [mcp] - summary: mcp / principal - x-marreq-variants: [server, cloud] - x-marreq-handler: mcp::principal - x-marreq-source: marreq-core/src/api/mcp.rs - x-marreq-guards: [McpPrincipalAuth] security: - cookieAuth: [] - bearerAuth: [] @@ -2045,47 +1146,27 @@ paths: /api/mcp/audit: post: operationId: mcp_audit - tags: [mcp] - summary: mcp / audit - x-marreq-variants: [server, cloud] - x-marreq-handler: mcp::audit - x-marreq-source: marreq-core/src/api/mcp.rs - x-marreq-guards: [StdioMcpAuditAuth] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - bearerAuth: [] x-marreq-authentication-note: Personal API tokens only; delegated OAuth and sessions rejected. /api/mcp/internal/audit: post: operationId: mcp_internal_audit - tags: [mcp] - summary: mcp / internal audit - x-marreq-variants: [server, cloud] - x-marreq-handler: mcp::internal_audit - x-marreq-source: marreq-core/src/api/mcp.rs - x-marreq-guards: [InternalMcpAudit, McpAuditAuth] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - mcpServiceAuth: [] /api/projects/{project_id}/me/permissions: get: operationId: members_get_my_permissions - tags: [members] - summary: members / get my permissions - x-marreq-variants: [server, cloud] - x-marreq-handler: members::get_my_permissions - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectProjectsRead] parameters: - in: path name: project_id @@ -2094,20 +1175,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/members: get: operationId: members_list_members - tags: [members] - summary: members / list members - x-marreq-variants: [server, cloud] - x-marreq-handler: members::list_members - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2116,20 +1190,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/members/{user_id}: put: operationId: members_set_member_role - tags: [members] - summary: members / set member role - x-marreq-variants: [server, cloud] - x-marreq-handler: members::set_member_role - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2142,19 +1209,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: members_remove_member - tags: [members] - summary: members / remove member - x-marreq-variants: [server, cloud] - x-marreq-handler: members::remove_member - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2167,20 +1227,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/reviewers: get: operationId: members_list_project_reviewers - tags: [members] - summary: members / list project reviewers - x-marreq-variants: [server, cloud] - x-marreq-handler: members::list_project_reviewers - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectProjectsRead] parameters: - in: path name: project_id @@ -2189,19 +1242,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] put: operationId: members_put_project_reviewers - tags: [members] - summary: members / put project reviewers - x-marreq-variants: [server, cloud] - x-marreq-handler: members::put_project_reviewers - x-marreq-source: marreq-core/src/api/members.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2210,60 +1256,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/: - get: - operationId: meta_api_root - tags: [meta] - summary: meta / api root - x-marreq-variants: [server, cloud] - x-marreq-handler: meta::api_root - x-marreq-source: marreq-core/src/api/meta.rs - x-marreq-guards: [] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/meta/health: - get: - operationId: meta_health - tags: [meta] - summary: meta / health - x-marreq-variants: [server, cloud] - x-marreq-handler: meta::health - x-marreq-source: marreq-core/src/api/meta.rs - x-marreq-guards: [] - security: [] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - status: {const: ok, type: string} - service: {const: marreq, type: string} - required: [status, service] - additionalProperties: true /api/meta/deployment: get: operationId: meta_deployment_info - tags: [meta] - summary: meta / deployment info - x-marreq-variants: [server, cloud] - x-marreq-handler: meta::deployment_info - x-marreq-source: marreq-core/src/api/meta.rs - x-marreq-guards: [] security: [] parameters: [] responses: @@ -2275,12 +1274,6 @@ paths: /api/meta/build: get: operationId: meta_build_info - tags: [meta] - summary: meta / build info - x-marreq-variants: [server, cloud] - x-marreq-handler: meta::build_info - x-marreq-source: marreq-core/src/api/meta.rs - x-marreq-guards: [] security: [] parameters: [] responses: @@ -2292,46 +1285,16 @@ paths: /api/notifications: get: operationId: notifications_list - tags: [notifications] - summary: notifications / list - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::list - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/notifications/unread-count: - get: - operationId: notifications_unread_count - tags: [notifications] - summary: notifications / unread count - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::unread_count - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/notifications/{id}/read: patch: operationId: notifications_mark_read - tags: [notifications] - summary: notifications / mark read - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::mark_read - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -2340,53 +1303,32 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/notifications/read-all: post: operationId: notifications_mark_all_read - tags: [notifications] - summary: notifications / mark all read - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::mark_all_read - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/notifications/preferences: get: operationId: notifications_get_preferences - tags: [notifications] - summary: notifications / get preferences - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::get_preferences - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/notifications/preferences/{project_id}: put: operationId: notifications_set_preference - tags: [notifications] - summary: notifications / set preference - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::set_preference - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: project_id @@ -2395,18 +1337,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: notifications_delete_preference - tags: [notifications] - summary: notifications / delete preference - x-marreq-variants: [server, cloud] - x-marreq-handler: notifications::delete_preference - x-marreq-source: marreq-core/src/api/notifications.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: project_id @@ -2415,19 +1350,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /.well-known/oauth-authorization-server: get: operationId: oauth_authorization_server_metadata - tags: [oauth] - summary: oauth / authorization server metadata - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::authorization_server_metadata - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [] parameters: [] responses: '200': @@ -2463,47 +1391,9 @@ paths: additionalProperties: true description: See implementation and http-api-contract.md for validation rules. security: [] - /.well-known/oauth-protected-resource: - get: - operationId: oauth_protected_resource_metadata - tags: [oauth] - summary: oauth / protected resource metadata - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::protected_resource_metadata - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - resource: *id001 - authorization_servers: - type: array - items: *id001 - scopes_supported: - type: array - items: *id001 - bearer_methods_supported: - type: array - items: *id001 - required: [resource, authorization_servers, scopes_supported, bearer_methods_supported] - additionalProperties: true - description: See implementation and http-api-contract.md for validation rules. - security: [] /.well-known/oauth-protected-resource/mcp: get: operationId: oauth_protected_resource_metadata_mcp - tags: [oauth] - summary: oauth / protected resource metadata mcp - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::protected_resource_metadata_mcp - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [] parameters: [] responses: '200': @@ -2530,149 +1420,63 @@ paths: /oauth/register: post: operationId: oauth_register - tags: [oauth] - summary: oauth / register - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::register - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: [] /oauth/authorize: get: operationId: oauth_authorize_page - tags: [oauth] - summary: oauth / authorize page - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::authorize_page - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [OptionalSessionUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: [] post: operationId: oauth_authorize_decision - tags: [oauth] - summary: oauth / authorize decision - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::authorize_decision - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [SessionUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /oauth/token: post: operationId: oauth_token - tags: [oauth] - summary: oauth / token - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::token - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: [] - /api/oauth/grants: - get: - operationId: oauth_grants - tags: [oauth] - summary: oauth / grants - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::grants - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [SessionUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/oauth/grants/{id}: - delete: - operationId: oauth_revoke_grant - tags: [oauth] - summary: oauth / revoke grant - x-marreq-variants: [server, cloud] - x-marreq-handler: oauth::revoke_grant - x-marreq-source: marreq-core/src/api/oauth.rs - x-marreq-guards: [SessionUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects: post: operationId: projects_create - tags: [projects] - summary: projects / create - x-marreq-variants: [server, cloud] - x-marreq-handler: projects::create - x-marreq-source: marreq-core/src/api/projects.rs - x-marreq-guards: [ApiUserOrBearer] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] get: operationId: projects_session_list_for_session - tags: [projects_session] - summary: projects session / list for session - x-marreq-variants: [server, cloud] - x-marreq-handler: projects_session::list_for_session - x-marreq-source: marreq-core/src/api/projects_session.rs - x-marreq-guards: [ProjectsRead] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}: patch: operationId: projects_update - tags: [projects] - summary: projects / update - x-marreq-variants: [server, cloud] - x-marreq-handler: projects::update - x-marreq-source: marreq-core/src/api/projects.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2681,19 +1485,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: projects_delete - tags: [projects] - summary: projects / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: projects::delete - x-marreq-source: marreq-core/src/api/projects.rs - x-marreq-guards: [ProjectAccess] parameters: - in: path name: project_id @@ -2702,19 +1499,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/archive: post: operationId: projects_archive - tags: [projects] - summary: projects / archive - x-marreq-variants: [server, cloud] - x-marreq-handler: projects::archive - x-marreq-source: marreq-core/src/api/projects.rs - x-marreq-guards: [ProjectAccess] parameters: - in: path name: project_id @@ -2723,19 +1513,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/unarchive: post: operationId: projects_unarchive - tags: [projects] - summary: projects / unarchive - x-marreq-variants: [server, cloud] - x-marreq-handler: projects::unarchive - x-marreq-source: marreq-core/src/api/projects.rs - x-marreq-guards: [ProjectAccess] parameters: - in: path name: project_id @@ -2744,19 +1527,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/project-from-path/{slug}: get: operationId: projects_session_project_from_path - tags: [projects_session] - summary: projects session / project from path - x-marreq-variants: [server, cloud] - x-marreq-handler: projects_session::project_from_path - x-marreq-source: marreq-core/src/api/projects_session.rs - x-marreq-guards: [OptionalSessionUser] parameters: - in: path name: slug @@ -2765,19 +1541,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/reports/types: get: operationId: reports_list_types - tags: [reports] - summary: reports / list types - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::list_types - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2786,20 +1555,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/report_templates: get: operationId: reports_list_templates - tags: [reports] - summary: reports / list templates - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::list_templates - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2808,19 +1570,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: reports_create_template - tags: [reports] - summary: reports / create template - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::create_template - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2829,20 +1584,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/report_templates/{template_id}: - get: - operationId: reports_get_template - tags: [reports] - summary: reports / get template - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::get_template - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] + patch: + operationId: reports_update_template parameters: - in: path name: project_id @@ -2855,44 +1603,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] - patch: - operationId: reports_update_template - tags: [reports] - summary: reports / update template - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::update_template - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: template_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: reports_delete_template - tags: [reports] - summary: reports / delete template - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::delete_template - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2905,20 +1621,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/reports/{file}: post: operationId: reports_generate - tags: [reports] - summary: reports / generate - x-marreq-variants: [server, cloud] - x-marreq-handler: reports::generate - x-marreq-source: marreq-core/src/api/reports.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -2931,49 +1640,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements/{req_id}/versions/{v1}/diff/{v2}: - get: - operationId: requirement_diff_diff_versions - tags: [requirement_diff] - summary: requirement diff / diff versions - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_diff::diff_versions - x-marreq-source: marreq-core/src/api/requirement_diff.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: v1 - required: true - schema: {type: integer} - - in: path - name: v2 - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: get: operationId: requirement_diff_diff_versions_by_project - tags: [requirement_diff] - summary: requirement diff / diff versions by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_diff::diff_versions_by_project - x-marreq-source: marreq-core/src/api/requirement_diff.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -2994,20 +1667,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: get: operationId: requirement_diff_diff_baseline_vs_current - tags: [requirement_diff] - summary: requirement diff / diff baseline vs current - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_diff::diff_baseline_vs_current - x-marreq-source: marreq-core/src/api/requirement_diff.rs - x-marreq-guards: [ProjectRequirementsAndBaselinesRead] parameters: - in: path name: project_id @@ -3024,20 +1690,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirement-version-links: post: operationId: requirement_version_links_create - tags: [requirement_version_links] - summary: requirement version links / create - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_version_links::create - x-marreq-source: marreq-core/src/api/requirement_version_links.rs - x-marreq-guards: [ProjectTraceabilityWrite] parameters: - in: path name: project_id @@ -3046,19 +1705,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] get: operationId: requirement_version_links_list - tags: [requirement_version_links] - summary: requirement version links / list - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_version_links::list - x-marreq-source: marreq-core/src/api/requirement_version_links.rs - x-marreq-guards: [ProjectTraceabilityRead] parameters: - in: path name: project_id @@ -3067,20 +1719,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirement-version-links/{link_id}: delete: operationId: requirement_version_links_delete - tags: [requirement_version_links] - summary: requirement version links / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_version_links::delete - x-marreq-source: marreq-core/src/api/requirement_version_links.rs - x-marreq-guards: [ProjectTraceabilityWrite] parameters: - in: path name: project_id @@ -3093,20 +1738,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirement-version-links/link-types: get: operationId: requirement_version_links_link_types - tags: [requirement_version_links] - summary: requirement version links / link types - x-marreq-variants: [server, cloud] - x-marreq-handler: requirement_version_links::link_types - x-marreq-source: marreq-core/src/api/requirement_version_links.rs - x-marreq-guards: [ProjectTraceabilityRead] parameters: - in: path name: project_id @@ -3115,53 +1753,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements: - get: - operationId: requirements_list - tags: [requirements] - summary: requirements / list - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::list - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - post: - operationId: requirements_create - tags: [requirements] - summary: requirements / create - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::create - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements: get: operationId: requirements_list_by_project - tags: [requirements] - summary: requirements / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::list_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3170,19 +1768,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: requirements_create_by_project - tags: [requirements] - summary: requirements / create by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::create_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsWrite] parameters: - in: path name: project_id @@ -3191,60 +1782,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/requirements/{id}: - get: - operationId: requirements_get - tags: [requirements] - summary: requirements / get - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] delete: operationId: requirements_delete - tags: [requirements] - summary: requirements / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::delete - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - patch: - operationId: requirements_patch_requirement - tags: [requirements] - summary: requirements / patch requirement - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::patch_requirement - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -3253,19 +1797,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/requirements/{id}: get: operationId: requirements_get_by_project - tags: [requirements] - summary: requirements / get by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3278,19 +1815,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] patch: operationId: requirements_patch_by_project - tags: [requirements] - summary: requirements / patch by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::patch_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsWrite] parameters: - in: path name: project_id @@ -3303,41 +1833,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements/{id}/versions: - get: - operationId: requirements_list_versions - tags: [requirements] - summary: requirements / list versions - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::list_versions - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/{id}/versions: get: operationId: requirements_list_versions_by_project - tags: [requirements] - summary: requirements / list versions by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::list_versions_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3350,45 +1852,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements/{req_id}/versions/{version_id}: - get: - operationId: requirements_get_version - tags: [requirements] - summary: requirements / get version - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get_version - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: get: operationId: requirements_get_version_by_project - tags: [requirements] - summary: requirements / get version by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get_version_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3405,41 +1875,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements/{id}/impacted_tests: - get: - operationId: requirements_get_impacted_tests - tags: [requirements] - summary: requirements / get impacted tests - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get_impacted_tests - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/{id}/impacted_tests: get: operationId: requirements_get_impacted_tests_by_project - tags: [requirements] - summary: requirements / get impacted tests by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::get_impacted_tests_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsAndVerificationsRead] parameters: - in: path name: project_id @@ -3452,45 +1894,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/requirements/{req_id}/versions/{version_id}/approval: - put: - operationId: requirements_set_version_approval - tags: [requirements] - summary: requirements / set version approval - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::set_version_approval - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: put: operationId: requirements_set_version_approval_by_project - tags: [requirements] - summary: requirements / set version approval by project - x-marreq-variants: [server, cloud] - x-marreq-handler: requirements::set_version_approval_by_project - x-marreq-source: marreq-core/src/api/requirements.rs - x-marreq-guards: [ProjectRequirementsApprove] parameters: - in: path name: project_id @@ -3507,20 +1917,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/saved_views: get: operationId: saved_views_list - tags: [saved_views] - summary: saved views / list - x-marreq-variants: [server, cloud] - x-marreq-handler: saved_views::list - x-marreq-source: marreq-core/src/api/saved_views.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3529,19 +1932,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: saved_views_create - tags: [saved_views] - summary: saved views / create - x-marreq-variants: [server, cloud] - x-marreq-handler: saved_views::create - x-marreq-source: marreq-core/src/api/saved_views.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -3550,20 +1946,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/saved_views/{view_id}: get: operationId: saved_views_get - tags: [saved_views] - summary: saved views / get - x-marreq-variants: [server, cloud] - x-marreq-handler: saved_views::get - x-marreq-source: marreq-core/src/api/saved_views.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -3576,19 +1965,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] patch: operationId: saved_views_update - tags: [saved_views] - summary: saved views / update - x-marreq-variants: [server, cloud] - x-marreq-handler: saved_views::update - x-marreq-source: marreq-core/src/api/saved_views.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -3601,19 +1983,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: saved_views_delete - tags: [saved_views] - summary: saved views / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: saved_views::delete - x-marreq-source: marreq-core/src/api/saved_views.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -3626,20 +2001,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirements/semantic_search: get: operationId: semantic_search_semantic_search - tags: [semantic_search] - summary: semantic search / semantic search - x-marreq-variants: [server, cloud] - x-marreq-handler: semantic_search::semantic_search - x-marreq-source: marreq-core/src/api/semantic_search.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3648,20 +2016,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirements/ask: post: operationId: semantic_search_ask - tags: [semantic_search] - summary: semantic search / ask - x-marreq-variants: [server, cloud] - x-marreq-handler: semantic_search::ask - x-marreq-source: marreq-core/src/api/semantic_search.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -3670,62 +2031,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] - /api/projects/{project_id}/requirements/reindex: - post: - operationId: semantic_search_reindex - tags: [semantic_search] - summary: semantic search / reindex - x-marreq-variants: [server, cloud] - x-marreq-handler: semantic_search::reindex - x-marreq-source: marreq-core/src/api/semantic_search.rs - x-marreq-guards: [AdminOnly, ProjectAccess] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/projects/{project_id}/requirements/index_status: get: operationId: semantic_search_index_status - tags: [semantic_search] - summary: semantic search / index status - x-marreq-variants: [server, cloud] - x-marreq-handler: semantic_search::index_status - x-marreq-source: marreq-core/src/api/semantic_search.rs - x-marreq-guards: [ProjectAccess] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/projects/{project_id}/requirements/semantic_search/status: - get: - operationId: semantic_search_search_status - tags: [semantic_search] - summary: semantic search / search status - x-marreq-variants: [server, cloud] - x-marreq-handler: semantic_search::search_status - x-marreq-source: marreq-core/src/api/semantic_search.rs - x-marreq-guards: [ProjectAccess] parameters: - in: path name: project_id @@ -3734,72 +2046,31 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/status: get: operationId: status_list_requirement_statuses - tags: [status] - summary: status / list requirement statuses - x-marreq-variants: [server, cloud] - x-marreq-handler: status::list_requirement_statuses - x-marreq-source: marreq-core/src/api/status.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: status_create_requirement_status - tags: [status] - summary: status / create requirement status - x-marreq-variants: [server, cloud] - x-marreq-handler: status::create_requirement_status - x-marreq-source: marreq-core/src/api/status.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/status/{id}: - get: - operationId: status_get_requirement_status - tags: [status] - summary: status / get requirement status - x-marreq-variants: [server, cloud] - x-marreq-handler: status::get_requirement_status - x-marreq-source: marreq-core/src/api/status.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] put: operationId: status_update_requirement_status - tags: [status] - summary: status / update requirement status - x-marreq-variants: [server, cloud] - x-marreq-handler: status::update_requirement_status - x-marreq-source: marreq-core/src/api/status.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -3808,18 +2079,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: status_delete_requirement_status - tags: [status] - summary: status / delete requirement status - x-marreq-variants: [server, cloud] - x-marreq-handler: status::delete_requirement_status - x-marreq-source: marreq-core/src/api/status.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -3828,19 +2092,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/requirements/{id}/trace_up: get: operationId: traceability_trace_up - tags: [traceability] - summary: traceability / trace up - x-marreq-variants: [server, cloud] - x-marreq-handler: traceability::trace_up - x-marreq-source: marreq-core/src/api/traceability.rs - x-marreq-guards: [ProjectRequirementsAndTraceabilityRead] parameters: - in: path name: project_id @@ -3853,20 +2110,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirements/{id}/trace_down: get: operationId: traceability_trace_down - tags: [traceability] - summary: traceability / trace down - x-marreq-variants: [server, cloud] - x-marreq-handler: traceability::trace_down - x-marreq-source: marreq-core/src/api/traceability.rs - x-marreq-guards: [ProjectRequirementsVerificationsAndTraceabilityRead] parameters: - in: path name: project_id @@ -3879,20 +2129,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/coverage_report: get: operationId: traceability_coverage_report - tags: [traceability] - summary: traceability / coverage report - x-marreq-variants: [server, cloud] - x-marreq-handler: traceability::coverage_report - x-marreq-source: marreq-core/src/api/traceability.rs - x-marreq-guards: [ProjectTraceabilityRead] parameters: - in: path name: project_id @@ -3901,37 +2144,23 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/traceability/clear_suspect: post: operationId: traceability_clear_suspect - tags: [traceability] - summary: traceability / clear suspect - x-marreq-variants: [server, cloud] - x-marreq-handler: traceability::clear_suspect - x-marreq-source: marreq-core/src/api/traceability.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/traceability/clear_suspect: post: operationId: traceability_clear_suspect_by_project - tags: [traceability] - summary: traceability / clear suspect by project - x-marreq-variants: [server, cloud] - x-marreq-handler: traceability::clear_suspect_by_project - x-marreq-source: marreq-core/src/api/traceability.rs - x-marreq-guards: [ProjectTraceabilityWrite] parameters: - in: path name: project_id @@ -3940,20 +2169,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/use-cases: get: operationId: use_cases_list - tags: [use_cases] - summary: use cases / list - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::list - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4042,12 +2264,6 @@ paths: semantics. post: operationId: use_cases_create - tags: [use_cases] - summary: use cases / create - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::create - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] parameters: - in: path name: project_id @@ -4081,8 +2297,7 @@ paths: - cookieAuth: [] - bearerAuth: [] x-marreq-bearer-scopes: ['use_cases:write'] - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf requestBody: required: true content: @@ -4104,12 +2319,6 @@ paths: /api/projects/{project_id}/use-cases/next-code: get: operationId: use_cases_next_code - tags: [use_cases] - summary: use cases / next code - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::next_code - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4141,12 +2350,6 @@ paths: /api/projects/{project_id}/use-cases/{id}: get: operationId: use_cases_get - tags: [use_cases] - summary: use cases / get - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::get - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4188,12 +2391,6 @@ paths: semantics. delete: operationId: use_cases_delete - tags: [use_cases] - summary: use cases / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::delete - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] security: - cookieAuth: [] - bearerAuth: [] @@ -4223,21 +2420,14 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf x-marreq-bearer-scopes: ['use_cases:write'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/draft: - get: - operationId: use_cases_get_draft - tags: [use_cases] - summary: use cases / get draft - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::get_draft - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] + put: + operationId: use_cases_put_draft security: - cookieAuth: [] - bearerAuth: [] @@ -4250,12 +2440,17 @@ paths: name: id required: true schema: {type: integer} - responses: + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: '200': description: Success content: application/json: - schema: {$ref: '#/components/schemas/use_cases_DraftView_response'} + schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} '400': {$ref: '#/components/responses/ApiError'} '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} @@ -4266,18 +2461,18 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_PutDraftRequest_request'} + x-marreq-bearer-scopes: ['use_cases:write'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. - put: - operationId: use_cases_put_draft - tags: [use_cases] - summary: use cases / put draft - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::put_draft - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] + delete: + operationId: use_cases_discard_draft security: - cookieAuth: [] - bearerAuth: [] @@ -4296,11 +2491,7 @@ paths: schema: {type: string} description: Required for session mutations; Bearer requests use scoped authentication instead. responses: - '200': - description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} + '204': {description: Deleted} '400': {$ref: '#/components/responses/ApiError'} '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} @@ -4311,25 +2502,13 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback - requestBody: - required: true - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_PutDraftRequest_request'} + x-marreq-csrf: session-csrf x-marreq-bearer-scopes: ['use_cases:write'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. - delete: - operationId: use_cases_discard_draft - tags: [use_cases] - summary: use cases / discard draft - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::discard_draft - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] + get: + operationId: use_cases_get_draft security: - cookieAuth: [] - bearerAuth: [] @@ -4342,13 +2521,12 @@ paths: name: id required: true schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. responses: - '204': {description: Deleted} + '200': + description: Success + content: + application/json: + schema: {$ref: '#/components/schemas/use_cases_DraftView_response'} '400': {$ref: '#/components/responses/ApiError'} '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} @@ -4359,21 +2537,13 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback - x-marreq-bearer-scopes: ['use_cases:write'] + x-marreq-bearer-scopes: ['use_cases:read'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/draft/operations: post: operationId: use_cases_draft_operations - tags: [use_cases] - summary: use cases / draft operations - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::draft_operations - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] parameters: - in: path name: project_id @@ -4418,17 +2588,10 @@ paths: items: {$ref: '#/components/schemas/UseCaseOperation'} additionalProperties: true required: [operations] - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/draft/publish: post: operationId: use_cases_publish - tags: [use_cases] - summary: use cases / publish - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::publish - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] parameters: - in: path name: project_id @@ -4477,17 +2640,10 @@ paths: - {type: 'null'} additionalProperties: true required: [base_revision] - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/versions: get: operationId: use_cases_list_versions - tags: [use_cases] - summary: use cases / list versions - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::list_versions - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4522,60 +2678,9 @@ paths: description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/versions/{version_id}: - get: - operationId: use_cases_get_version - tags: [use_cases] - summary: use cases / get version - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::get_version - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} - responses: - '200': - description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_VersionView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. /api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: put: operationId: use_cases_set_version_approval - tags: [use_cases] - summary: use cases / set version approval - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::set_version_approval - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesApprove] security: - cookieAuth: [] - bearerAuth: [] @@ -4613,8 +2718,7 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf requestBody: required: true content: @@ -4627,12 +2731,6 @@ paths: /api/projects/{project_id}/use-cases/{id}/links: get: operationId: use_cases_list_links - tags: [use_cases] - summary: use cases / list links - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::list_links - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4669,12 +2767,6 @@ paths: semantics. post: operationId: use_cases_create_link - tags: [use_cases] - summary: use cases / create link - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::create_link - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] parameters: - in: path name: project_id @@ -4727,17 +2819,10 @@ paths: - *id002 - {type: 'null'} additionalProperties: true - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/links/{link_id}: delete: operationId: use_cases_delete_link - tags: [use_cases] - summary: use cases / delete link - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::delete_link - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] security: - cookieAuth: [] - bearerAuth: [] @@ -4771,8 +2856,7 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf x-marreq-bearer-scopes: ['use_cases:write'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict @@ -4780,12 +2864,6 @@ paths: /api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: post: operationId: use_cases_clear_link_suspect - tags: [use_cases] - summary: use cases / clear link suspect - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::clear_link_suspect - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] security: - cookieAuth: [] - bearerAuth: [] @@ -4823,8 +2901,7 @@ paths: '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body.'} '500': {$ref: '#/components/responses/ApiError'} - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf x-marreq-bearer-scopes: ['use_cases:write'] description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict @@ -4832,12 +2909,6 @@ paths: /api/projects/{project_id}/requirements/{requirement_id}/use-cases: get: operationId: use_cases_requirement_use_cases - tags: [use_cases] - summary: use cases / requirement use cases - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::requirement_use_cases - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4875,12 +2946,6 @@ paths: /api/projects/{project_id}/verifications/{verification_id}/use-cases: get: operationId: use_cases_verification_use_cases - tags: [use_cases] - summary: use cases / verification use cases - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::verification_use_cases - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -4918,12 +2983,6 @@ paths: /api/projects/{project_id}/use-cases/{id}/draft/diff: get: operationId: use_cases_draft_diff - tags: [use_cases] - summary: use cases / draft diff - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::draft_diff - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] parameters: - in: path name: project_id @@ -4959,12 +3018,6 @@ paths: /api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: get: operationId: use_cases_version_diff - tags: [use_cases] - summary: use cases / version diff - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::version_diff - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] parameters: - in: path name: project_id @@ -5008,12 +3061,6 @@ paths: /api/projects/{project_id}/use-cases/{id}/comments: get: operationId: use_cases_list_comments - tags: [use_cases] - summary: use cases / list comments - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::list_comments - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -5064,12 +3111,6 @@ paths: semantics. post: operationId: use_cases_create_comment - tags: [use_cases] - summary: use cases / create comment - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::create_comment - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesWrite] parameters: - in: path name: project_id @@ -5122,17 +3163,10 @@ paths: - {type: 'null'} additionalProperties: true required: [body] - x-marreq-csrf: session mutations require matching private csrf cookie and X-CSRF-Token; login/logout allow - trusted Origin/Referer fallback + x-marreq-csrf: session-csrf /api/projects/{project_id}/baselines/{baseline_id}/use-cases: get: operationId: use_cases_baseline_use_cases - tags: [use_cases] - summary: use cases / baseline use cases - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::baseline_use_cases - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] security: - cookieAuth: [] - bearerAuth: [] @@ -5167,113 +3201,9 @@ paths: description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/baselines/{baseline_id}/use-cases/{id}/diff/current: - get: - operationId: use_cases_baseline_use_case_diff - tags: [use_cases] - summary: use cases / baseline use case diff - x-marreq-variants: [server, cloud] - x-marreq-handler: use_cases::baseline_use_case_diff - x-marreq-source: marreq-core/src/api/use_cases.rs - x-marreq-guards: [ProjectUseCasesRead] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: query - name: include_unchanged - required: false - schema: {type: boolean} - responses: - '200': - description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/UseCaseDiff'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:read'] - /api/users: - get: - operationId: users_list - tags: [users] - summary: users / list - x-marreq-variants: [server, cloud] - x-marreq-handler: users::list - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - post: - operationId: users_create - tags: [users] - summary: users / create - x-marreq-variants: [server, cloud] - x-marreq-handler: users::create - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] - parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] /api/users/{id}: - get: - operationId: users_get - tags: [users] - summary: users / get - x-marreq-variants: [server, cloud] - x-marreq-handler: users::get - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] put: operationId: users_update - tags: [users] - summary: users / update - x-marreq-variants: [server, cloud] - x-marreq-handler: users::update - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] parameters: - in: path name: id @@ -5282,18 +3212,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: users_delete - tags: [users] - summary: users / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: users::delete - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] parameters: - in: path name: id @@ -5302,19 +3225,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/users/{id}/password: put: operationId: users_set_password - tags: [users] - summary: users / set password - x-marreq-variants: [server, cloud] - x-marreq-handler: users::set_password - x-marreq-source: marreq-core/src/api/users.rs - x-marreq-guards: [AdminOnly] parameters: - in: path name: id @@ -5323,19 +3239,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/projects/{project_id}/verifications/{verification_id}/control: get: operationId: verification_control_get_verification_control - tags: [verification_control] - summary: verification control / get verification control - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_control::get_verification_control - x-marreq-source: marreq-core/src/api/verification_control.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -5348,19 +3257,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] put: operationId: verification_control_put_verification_control - tags: [verification_control] - summary: verification control / put verification control - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_control::put_verification_control - x-marreq-source: marreq-core/src/api/verification_control.rs - x-marreq-guards: [ProjectVerificationsWrite] parameters: - in: path name: project_id @@ -5373,20 +3275,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirements/{requirement_id}/close_out: get: operationId: verification_control_get_requirement_close_out - tags: [verification_control] - summary: verification control / get requirement close out - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_control::get_requirement_close_out - x-marreq-source: marreq-core/src/api/verification_control.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -5399,20 +3294,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/close_out: get: operationId: verification_control_get_project_close_out - tags: [verification_control] - summary: verification control / get project close out - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_control::get_project_close_out - x-marreq-source: marreq-core/src/api/verification_control.rs - x-marreq-guards: [ProjectRequirementsRead] parameters: - in: path name: project_id @@ -5421,20 +3309,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/requirements/{requirement_id}/compliance: put: operationId: verification_control_put_requirement_compliance - tags: [verification_control] - summary: verification control / put requirement compliance - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_control::put_requirement_compliance - x-marreq-source: marreq-core/src/api/verification_control.rs - x-marreq-guards: [ProjectRequirementsWrite] parameters: - in: path name: project_id @@ -5447,20 +3328,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verifications/{id}/snapshots: get: operationId: verification_diff_list_snapshots_by_project - tags: [verification_diff] - summary: verification diff / list snapshots by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_diff::list_snapshots_by_project - x-marreq-source: marreq-core/src/api/verification_diff.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -5473,20 +3347,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: get: operationId: verification_diff_diff_snapshots_by_project - tags: [verification_diff] - summary: verification diff / diff snapshots by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_diff::diff_snapshots_by_project - x-marreq-source: marreq-core/src/api/verification_diff.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -5507,20 +3374,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: get: operationId: verification_diff_diff_baseline_vs_current - tags: [verification_diff] - summary: verification diff / diff baseline vs current - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_diff::diff_baseline_vs_current - x-marreq-source: marreq-core/src/api/verification_diff.rs - x-marreq-guards: [ProjectVerificationsAndBaselinesRead] parameters: - in: path name: project_id @@ -5537,20 +3397,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verification-methods: get: operationId: verification_methods_list_by_project - tags: [verification_methods] - summary: verification methods / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_methods::list_by_project - x-marreq-source: marreq-core/src/api/verification_methods.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -5559,19 +3412,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: verification_methods_create_by_project - tags: [verification_methods] - summary: verification methods / create by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_methods::create_by_project - x-marreq-source: marreq-core/src/api/verification_methods.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -5588,12 +3434,6 @@ paths: /api/projects/{project_id}/verification-methods/{method_id}: put: operationId: verification_methods_update_by_project - tags: [verification_methods] - summary: verification methods / update by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_methods::update_by_project - x-marreq-source: marreq-core/src/api/verification_methods.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -5606,19 +3446,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] delete: operationId: verification_methods_delete_by_project - tags: [verification_methods] - summary: verification methods / delete by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_methods::delete_by_project - x-marreq-source: marreq-core/src/api/verification_methods.rs - x-marreq-guards: [ProjectAccessOrBearer] parameters: - in: path name: project_id @@ -5631,73 +3464,32 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/verification-status: get: operationId: verification_status_list_verification_statuses - tags: [verification_status] - summary: verification status / list verification statuses - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_status::list_verification_statuses - x-marreq-source: marreq-core/src/api/verification_status.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: verification_status_create_verification_status - tags: [verification_status] - summary: verification status / create verification status - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_status::create_verification_status - x-marreq-source: marreq-core/src/api/verification_status.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/verification-status/{id}: - get: - operationId: verification_status_get_verification_status - tags: [verification_status] - summary: verification status / get verification status - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_status::get_verification_status - x-marreq-source: marreq-core/src/api/verification_status.rs - x-marreq-guards: [ApiUser] - parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] put: operationId: verification_status_update_verification_status - tags: [verification_status] - summary: verification status / update verification status - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_status::update_verification_status - x-marreq-source: marreq-core/src/api/verification_status.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -5706,18 +3498,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: verification_status_delete_verification_status - tags: [verification_status] - summary: verification status / delete verification status - x-marreq-variants: [server, cloud] - x-marreq-handler: verification_status::delete_verification_status - x-marreq-source: marreq-core/src/api/verification_status.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -5726,35 +3511,21 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] /api/verifications: get: operationId: verifications_list - tags: [verifications] - summary: verifications / list - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::list - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] post: operationId: verifications_create - tags: [verifications] - summary: verifications / create - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::create - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ApiUser] parameters: [] responses: default: {description: Wire behavior has not yet been verified.} @@ -5766,12 +3537,6 @@ paths: /api/projects/{project_id}/verifications: get: operationId: verifications_list_by_project - tags: [verifications] - summary: verifications / list by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::list_by_project - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -5780,19 +3545,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] post: operationId: verifications_create_by_project - tags: [verifications] - summary: verifications / create by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::create_by_project - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ProjectVerificationsWrite] parameters: - in: path name: project_id @@ -5801,20 +3559,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/projects/{project_id}/verifications/{id}: get: operationId: verifications_get_by_project - tags: [verifications] - summary: verifications / get by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::get_by_project - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ProjectVerificationsRead] parameters: - in: path name: project_id @@ -5827,19 +3578,12 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] put: operationId: verifications_update_by_project - tags: [verifications] - summary: verifications / update by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::update_by_project - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ProjectVerificationsWrite] parameters: - in: path name: project_id @@ -5852,20 +3596,13 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - bearerAuth: [] /api/verifications/{id}: get: operationId: verifications_get - tags: [verifications] - summary: verifications / get - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::get - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -5874,18 +3611,11 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] delete: operationId: verifications_delete - tags: [verifications] - summary: verifications / delete - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::delete - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ApiUser] parameters: - in: path name: id @@ -5894,20 +3624,17 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - /api/verifications/{id}/field: + /api/projects/{project_id}/verifications/{id}/field: post: - operationId: verifications_update_field - tags: [verifications] - summary: verifications / update field - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::update_field - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ApiUser] + operationId: verifications_update_field_by_project parameters: + - in: path + name: project_id + required: true + schema: {type: integer} - in: path name: id required: true @@ -5915,26 +3642,32 @@ paths: responses: default: {description: Wire behavior has not yet been verified.} x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. + description: Wire contract pending audit; required consumers fail verification. security: - cookieAuth: [] - /api/projects/{project_id}/verifications/{id}/field: - post: - operationId: verifications_update_field_by_project - tags: [verifications] - summary: verifications / update field by project - x-marreq-variants: [server, cloud] - x-marreq-handler: verifications::update_field_by_project - x-marreq-source: marreq-core/src/api/verifications.rs - x-marreq-guards: [ProjectVerificationsWrite] + - bearerAuth: [] + /api/auth/csrf: + get: + operationId: auth_auth_csrf + security: [] + parameters: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + csrf_token: {type: string} + required: [csrf_token] + additionalProperties: true + /api/auth/identities/{identity_id}: + delete: + operationId: auth_auth_identity_delete parameters: - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id + name: identity_id required: true schema: {type: integer} responses: @@ -5944,16 +3677,10 @@ paths: audit before compatibility can be established. security: - cookieAuth: [] - - bearerAuth: [] /api/auth/register: post: operationId: auth_public_auth_register - tags: [auth_public] - summary: auth public / auth register x-marreq-variants: [cloud] - x-marreq-handler: auth_public::auth_register - x-marreq-source: marreq-cloud/src/api/auth_public.rs - x-marreq-guards: [] parameters: [] responses: '201': @@ -5988,26 +3715,111 @@ paths: schema: type: object properties: - username: *id001 - name: *id001 - email: *id001 - password: *id001 + username: &id005 {type: string} + name: *id005 + email: *id005 + password: *id005 required: [username, name, email, password] additionalProperties: true + /api/auth/login: + post: + operationId: auth_auth_login + parameters: [] + responses: + '200': + description: Authenticated session established + content: + application/json: + schema: + type: object + properties: + status: {type: string, const: ok} + user: {$ref: '#/components/schemas/entities_User_response'} + required: [status, user] + '400': {$ref: '#/components/responses/ApiError'} + '403': {description: CSRF failure; body may be framework generated} + '410': {$ref: '#/components/responses/ApiError'} + '422': {description: Rocket JSON validation failure} + '500': {$ref: '#/components/responses/ApiError'} + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + x-marreq-csrf: auth-origin-fallback + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [username, password] + properties: + username: {type: string} + password: {type: string} + /api/users: + post: + operationId: users_create + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + get: + operationId: users_list + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/projects/{project_id}/attachments/{attachment_id}: + delete: + operationId: attachments_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + - bearerAuth: [] + /api/auth/external/{provider}/link: + post: + operationId: auth_auth_external_link_start + parameters: + - in: path + name: provider + required: true + schema: {type: string} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] /api/auth/verify-email: get: operationId: auth_public_auth_verify_email - tags: [auth_public] - summary: auth public / auth verify email x-marreq-variants: [cloud] - x-marreq-handler: auth_public::auth_verify_email - x-marreq-source: marreq-cloud/src/api/auth_public.rs - x-marreq-guards: [] parameters: - in: query name: token required: true - schema: *id001 + schema: *id005 responses: '200': description: Success @@ -6024,15 +3836,36 @@ paths: description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. security: [] + /api/auth/logout: + post: + operationId: auth_auth_logout + parameters: [] + responses: + '200': + description: Session and CSRF cookies cleared + content: + application/json: + schema: + type: object + properties: + status: {type: string, const: ok} + required: [status] + '403': {description: CSRF failure; body may be framework generated} + '500': {$ref: '#/components/responses/ApiError'} + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: [] + x-marreq-csrf: auth-origin-fallback + requestBody: + required: false + description: Optional ignored body; the SPA sends an empty JSON object. + content: + application/json: + schema: {} /api/auth/forgot-password: post: operationId: auth_public_auth_forgot_password - tags: [auth_public] - summary: auth public / auth forgot password x-marreq-variants: [cloud] - x-marreq-handler: auth_public::auth_forgot_password - x-marreq-source: marreq-cloud/src/api/auth_public.rs - x-marreq-guards: [] parameters: [] responses: '200': @@ -6057,18 +3890,72 @@ paths: schema: type: object properties: - email: *id001 + email: *id005 required: [email] additionalProperties: true + /api/notifications/unread-count: + get: + operationId: notifications_unread_count + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/auth/change-password: + post: + operationId: auth_auth_change_password + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/oauth/grants: + get: + operationId: oauth_grants + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/oauth/grants/{id}: + delete: + operationId: oauth_revoke_grant + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] + /api/dashboard: + get: + operationId: dashboard_dashboard_json + parameters: [] + responses: + default: {description: Wire behavior has not yet been verified.} + x-marreq-unverified: true + description: Route inventory only. Request/response serialization, status codes and authorization require + audit before compatibility can be established. + security: + - cookieAuth: [] /api/auth/reset-password: post: operationId: auth_public_auth_reset_password - tags: [auth_public] - summary: auth public / auth reset password x-marreq-variants: [cloud] - x-marreq-handler: auth_public::auth_reset_password - x-marreq-source: marreq-cloud/src/api/auth_public.rs - x-marreq-guards: [] parameters: [] responses: '200': @@ -6093,8 +3980,8 @@ paths: schema: type: object properties: - token: *id001 - new_password: *id001 + token: *id005 + new_password: *id005 required: [token, new_password] additionalProperties: true components: @@ -6115,7 +4002,6 @@ components: items: {$ref: '#/components/schemas/external_PublicAuthProvider_response'} additionalProperties: true required: [password_enabled, external] - x-rust-model: marreq-core/src/auth/external.rs::ProviderDiscovery external_PublicAuthProvider_response: type: object properties: @@ -6123,7 +4009,6 @@ components: display_name: {type: string} additionalProperties: true required: [id, display_name] - x-rust-model: marreq-core/src/auth/external.rs::PublicAuthProvider entities_User_response: type: object properties: @@ -6137,7 +4022,6 @@ components: email_verified: {type: boolean} additionalProperties: true required: [id, username, name, email, creation_date, last_login, is_admin, email_verified] - x-rust-model: marreq-core/src/models/entities.rs::User use_cases_UseCaseRow_response: type: object properties: @@ -6166,14 +4050,12 @@ components: additionalProperties: true required: [id, reference_code, title, primary_actor, step_count, alternative_count, exception_count, approval_state, current_version_number, has_draft, link_count, suspect_count, updated_at] - x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseRow use_cases_NextCode_response: type: object properties: reference_code: {type: string} additionalProperties: true required: [reference_code] - x-rust-model: marreq-core/src/api/use_cases.rs::NextCode use_cases_UseCaseDetail_response: type: object properties: @@ -6197,7 +4079,6 @@ components: - {type: 'null'} additionalProperties: true required: [id, project_id, reference_code, title, created_by, created_at, updated_at, current_version, draft] - x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseDetail use_cases_VersionView_response: type: object properties: @@ -6245,7 +4126,6 @@ components: additionalProperties: true required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, created_at, reviewed_by, reviewed_at, approved_by, approved_at, document, labels] - x-rust-model: marreq-core/src/api/use_cases.rs::VersionView use_cases_VersionSummary_response: type: object properties: @@ -6289,7 +4169,6 @@ components: additionalProperties: true required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, created_at, reviewed_by, reviewed_at, approved_by, approved_at] - x-rust-model: marreq-core/src/api/use_cases.rs::VersionSummary use_cases_DraftSummary_response: type: object properties: @@ -6309,7 +4188,6 @@ components: updated_at: {type: string} additionalProperties: true required: [revision, base_version_id, updated_by, updated_by_name, updated_at] - x-rust-model: marreq-core/src/api/use_cases.rs::DraftSummary use_cases_DraftView_response: type: object properties: @@ -6334,7 +4212,6 @@ components: additionalProperties: {type: string} additionalProperties: true required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels] - x-rust-model: marreq-core/src/api/use_cases.rs::DraftView use_cases_PutDraftRequest_request: type: object properties: @@ -6345,7 +4222,6 @@ components: document: {$ref: '#/components/schemas/UseCaseDocument'} additionalProperties: true required: [document] - x-rust-model: marreq-core/src/api/use_cases.rs::PutDraftRequest use_cases_DraftChangeView_response: type: object properties: @@ -6374,14 +4250,12 @@ components: additionalProperties: true required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels, effects] - x-rust-model: marreq-core/src/api/use_cases.rs::DraftChangeView use_cases_SetApprovalRequest_request: type: object properties: state: {type: string} additionalProperties: true required: [state] - x-rust-model: marreq-core/src/api/use_cases.rs::SetApprovalRequest use_cases_LinkView_response: type: object properties: @@ -6437,7 +4311,6 @@ components: required: [id, element_id, element_label, target_type, requirement_id, verification_id, target_reference_code, target_title, rationale, suspect, suspect_at, suspect_reason, linked_requirement_version_id, current_requirement_version_id, created_by, created_at] - x-rust-model: marreq-core/src/api/use_cases.rs::LinkView use_cases_ClearSuspectResponse_response: type: object properties: @@ -6445,7 +4318,6 @@ components: cleared: {type: boolean} additionalProperties: true required: [status, cleared] - x-rust-model: marreq-core/src/api/use_cases.rs::ClearSuspectResponse use_cases_UseCaseRef_response: type: object properties: @@ -6464,7 +4336,6 @@ components: suspect: {type: boolean} additionalProperties: true required: [use_case_id, reference_code, title, link_id, element_id, element_label, suspect] - x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseRef use_cases_UseCaseCommentView_response: type: object properties: @@ -6489,7 +4360,6 @@ components: additionalProperties: true required: [id, use_case_id, use_case_version_id, element_id, element_label, author_id, author_name, body, created_at] - x-rust-model: marreq-core/src/api/use_cases.rs::UseCaseCommentView use_cases_BaselineUseCaseView_response: type: object properties: @@ -6515,7 +4385,6 @@ components: additionalProperties: true required: [use_case_id, reference_code, title, primary_actor, version_id, version_number, approval_state, current_version_id, current_version_number, link_count] - x-rust-model: marreq-core/src/api/use_cases.rs::BaselineUseCaseView ApiError: type: object required: [status, error, message] @@ -6527,16 +4396,16 @@ components: BuildInfo: type: object properties: - backend_version: *id005 - backend_git_sha: *id005 + backend_version: &id006 {type: string} + backend_git_sha: *id006 deployment_mode: type: string enum: [server, cloud] frontend_compatibility: type: object properties: - min_version: *id005 - max_version: *id005 + min_version: *id006 + max_version: *id006 required: [min_version, max_version] additionalProperties: true required: [backend_version, backend_git_sha, deployment_mode, frontend_compatibility] @@ -6544,8 +4413,8 @@ components: UseCaseElement: type: object properties: - id: &id006 {type: string} - text: *id006 + id: &id007 {type: string} + text: *id007 additionalProperties: false required: [id, text] UseCaseBranchEnd: @@ -6553,7 +4422,7 @@ components: - type: object properties: type: {const: rejoin, type: string} - step: *id006 + step: *id007 additionalProperties: false required: [type, step] - type: object @@ -6568,17 +4437,17 @@ components: type: object properties: schema: {type: string, const: marreq.use-case.v1} - title: *id006 + title: *id007 primary_actor: anyOf: - - *id006 + - *id007 - {type: 'null'} secondary_actors: type: array - items: *id006 + items: *id007 summary: anyOf: - - *id006 + - *id007 - {type: 'null'} preconditions: type: array @@ -6597,12 +4466,12 @@ components: UseCaseBranch: type: object properties: - id: *id006 + id: *id007 kind: type: string enum: [alternative, exception] - title: *id006 - from_step: *id006 + title: *id007 + from_step: *id007 end: {$ref: '#/components/schemas/UseCaseBranchEnd'} steps: type: array @@ -6614,28 +4483,28 @@ components: - type: object properties: type: {type: string, const: relabelled} - id: *id006 - from: *id006 - to: *id006 + id: *id007 + from: *id007 + to: *id007 additionalProperties: true required: [type, id, from, to] - type: object properties: type: {type: string, const: removed} - id: *id006 + id: *id007 additionalProperties: true required: [type, id] - type: object properties: type: {type: string, const: rebranched} - id: *id006 - from_step: *id006 + id: *id007 + from_step: *id007 additionalProperties: true required: [type, id, from_step] - type: object properties: type: {type: string, const: created} - id: *id006 + id: *id007 additionalProperties: true required: [type, id] DeploymentInfo: @@ -6644,12 +4513,12 @@ components: mode: type: string enum: [server, cloud] - allows_self_registration: &id007 {type: boolean} - requires_email_verification: *id007 - allows_admin_promotion: *id007 - assigns_personal_workspace: *id007 - allows_self_administered_user_creation: *id007 - allows_database_backup: *id007 + allows_self_registration: &id008 {type: boolean} + requires_email_verification: *id008 + allows_admin_promotion: *id008 + assigns_personal_workspace: *id008 + allows_self_administered_user_creation: *id008 + allows_database_backup: *id008 required: [mode, allows_self_registration, requires_email_verification, allows_admin_promotion, assigns_personal_workspace, allows_self_administered_user_creation, allows_database_backup] additionalProperties: true @@ -6669,22 +4538,21 @@ components: - {type: 'null'} additionalProperties: true required: [user_id, authentication_type, principal_id, client_id, grant_id] - x-rust-model: marreq-core/src/api/mcp.rs::McpPrincipalResponse UseCaseDocumentResponse: type: object properties: schema: {type: string, const: marreq.use-case.v1} - title: &id008 {type: string} + title: &id009 {type: string} primary_actor: anyOf: - - *id008 + - *id009 - {type: 'null'} secondary_actors: type: array - items: *id008 + items: *id009 summary: anyOf: - - *id008 + - *id009 - {type: 'null'} preconditions: type: array @@ -6703,12 +4571,12 @@ components: UseCaseBranchResponse: type: object properties: - id: &id009 {type: string} + id: &id010 {type: string} kind: type: string enum: [alternative, exception] - title: *id009 - from_step: *id009 + title: *id010 + from_step: *id010 end: {$ref: '#/components/schemas/UseCaseBranchEnd'} steps: type: array @@ -6954,13 +4822,13 @@ components: additionalProperties: type: object properties: - old: &id010 + old: &id011 oneOf: - *id002 - {type: 'null'} - type: array items: *id002 - new: *id010 + new: *id011 additionalProperties: true required: [old, new] elements: @@ -6984,6 +4852,5 @@ components: content: application/json: schema: {$ref: '#/components/schemas/ApiError'} -x-marreq-contract-complete: false security: - cookieAuth: [] diff --git a/docs/developer/use-cases-api.md b/docs/developer/use-cases-api.md index 6340723c..3920ac80 100644 --- a/docs/developer/use-cases-api.md +++ b/docs/developer/use-cases-api.md @@ -167,13 +167,12 @@ Comments are immutable. `@username` mentions of project members send a `mentione - **Project bundle.** `bundle.json` has an optional `use_cases` section (see the HTTP API contract). - **MCP.** `list_use_cases`, `get_use_case`, `get_use_case_diff` and `list_use_case_links` (`use_cases:read`); `create_use_case`, `edit_use_case_draft`, `publish_use_case_draft` and `link_use_case_element` (`use_cases:write`, `MARREQ_MODE=draft_write`). Approval is not exposed to agents. See [mcp-setup.md](mcp-setup.md). -## Machine-readable contract - -The use-case routes and wire schemas are described in `openapi.yaml`, including -request defaults, response nullability, draft-operation tagged unions, version -publishing, links, comments and diffs. Derived TypeScript models are generated by -`scripts/contracts/generate.py`; do not hand-edit `generated-contract.ts`. -The isolated backend contract smoke test covers representative list/create/draft -operations, delegated scope rejection and project boundaries. Full API contract -coverage and broader cross-version evidence remain tracked in the P0 audit in -[http-api-contract.md](http-api-contract.md#p0-contract-hardening-audit-and-current-coverage-423). +## Executable contract checks + +Use-case request/response definitions are in `openapi.yaml`; consumer declarations +are generated with standard `openapi-typescript` through +`node scripts/generate-api-types.mjs`. Native Rust tests and +`scripts/test-api-contracts.sh` check representative real read/write, session, +Bearer-scope and project-boundary behavior. See the +[contract workflow](http-api-contract.md#consumer-aware-contract-verification-423--424) +for scope limits and the required schema-update procedure. diff --git a/docs/releases.md b/docs/releases.md index 645e74e7..e749832b 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -82,130 +82,75 @@ Images carry OCI label `org.opencontainers.image.version`. When `MARREQ_VERSION` is unset or empty, each artifact falls back to its manifest version. A leading `v` is stripped so the value stays plain `X.Y.Z` for the compatibility check. Local images: `make docker-server MARREQ_VERSION=0.1.3`. -## API compatibility gate and retained artifact contracts (#423 P0) - -The P0 bootstrap is **not release-ready**: 206 HTTP operations still lack audited -wire contracts. `API contracts` CI validates the document, generated artifacts, -source-review evidence, both compiled route inventories, negative historical -fixtures and representative real producer/consumer exchanges. Release publication -also requires `scripts/contracts/check.py --require-complete`, which currently -fails intentionally. This must remain a draft until the coverage gaps described in -[the HTTP contract audit](developer/http-api-contract.md#p0-contract-hardening-audit-and-current-coverage-423) -are closed. - -### Compatibility policy - -Component SemVer describes each artifact independently. Equal versions are never -required. A consumer declares `requires_backend_min` and optionally an inclusive -`requires_backend_max`; frontend and MCP metadata are validated independently. -The backend's existing frontend acceptance range is also checked. Stable -`X.Y.Z` versions are supported; prerelease and wildcard ranges fail closed. -Omitting an upper bound is appropriate only with executable retained-contract -checks; an upper bound is a temporary tested-support ceiling, not proof that -all versions inside it work. Existing runtime `CompatibilityBanner` warnings are -preserved for manually assembled deployments. - -A producer must continue accepting every previously valid consumer request and -returning responses accepted by that released consumer. Additive optional request -fields and additive response fields are compatible when the consumer accepts -unknown properties. Removing an endpoint or guaranteed response property, -changing a field type/nullability, adding a required request field, restricting a -request enum, extending a response enum used by exhaustive consumers, changing -status/media/header behavior or changing authentication/scopes is potentially -breaking. Breaking changes require a new API-compatible deployment combination -and a component SemVer breaking release; while 0.x, treat minor changes with the -same explicit care rather than assuming all 0.x releases interoperate. Unknown -changed schema constraints/unions are rejected conservatively for manual review. - -Previously released consumers retain their own contracts and policy. A newer -backend must satisfy those old contracts, not regenerated contracts from current -`main`. During rolling upgrades, check each backend/consumer combination that -can coexist, including old and new replicas; upgrade consumers first only when -the old backend also passes. No gate establishes database migration safety; -that remains a separate P0 workstream in #423. - -### Export and retain an exact component contract - -After committing audited contract inputs on the component's source commit: +## Exact artifact API compatibility (#423) + +The Rust CLI checks each consumer's own retained contract, never current-main +substitutes. A bundle contains only its selected operations and reachable schemas, +component/version/source SHA and compatibility policy. Backend bundles contain +verified producer operations for one variant; unknown unconsumed routes are not +invented. Consumer export fails on any required unknown/unverified dependency. + +Export from the artifact's clean source commit: ```sh -python3 scripts/contracts/bundle.py --component backend --variant server \ - --version 0.2.4 --output backend-0.2.4.json -python3 scripts/contracts/bundle.py --component frontend \ - --version 0.2.1 --output frontend-0.2.1.json -python3 scripts/contracts/bundle.py --component mcp \ - --version 0.3.0 --output mcp-0.3.0.json +cargo run --locked -p marreq-contracts -- bundle --component backend --variant server --version 0.2.4 --output backend.json +cargo run --locked -p marreq-contracts -- bundle --component frontend --version 0.2.1 --output frontend.json +cargo run --locked -p marreq-contracts -- bundle --component mcp --version 0.3.0 --output mcp.json ``` -Run each command on **that artifact's own source commit**, not all on current -`main`. The exporter refuses dirty audited inputs and records the exact source -SHA. Retain the JSON next to the versioned image/release with its SHA-256. Images -also carry their build-time OpenAPI and compatibility metadata. The exporter can -record incomplete bootstrap evidence for review, but the gate cannot certify it. -Old releases without retained evidence require a real audit on their source -commit; missing evidence never falls back to current sources. +Each command must run on **that component version's actual source**, not all on +main. Retain the output beside its release artifact. `marreq.contract.v2` requires +consumer-specific evidence; the old whole-backend bootstrap bundle is not +retrospectively certified. Never fabricate contracts for already published images. -### Verify the resulting deployment, including unchanged artifacts - -Prepare a `deployment.json` with exactly these top-level fields: +A `marreq.deployment-contract.v2` plan names `variant` (`server`/`cloud`) and +`backend`, `frontend`, `mcp`. Each descriptor contains exactly `file`, `sha256`, +`version`, `source_sha`. Files must remain inside the plan directory. SHA-256 pins +**contract bytes, not Docker image provenance**; a trusted orchestrator must bind +the bundle/source to the actual image digest. This prerequisite does not implement +that future #423 release/provenance/CD architecture. ```json { - "format": "marreq.deployment-contract.v1", + "format": "marreq.deployment-contract.v2", "variant": "server", - "backend": { - "file": "backend-0.2.4.json", - "sha256": "<64 lowercase hex characters>", - "version": "0.2.4", - "source_sha": "<40 lowercase hex characters>" - }, - "frontend": { - "file": "frontend-0.2.1.json", - "sha256": "<64 lowercase hex characters>", - "version": "0.2.1", - "source_sha": "<40 lowercase hex characters>" - }, - "mcp": { - "file": "mcp-0.3.0.json", - "sha256": "<64 lowercase hex characters>", - "version": "0.3.0", - "source_sha": "<40 lowercase hex characters>" - } + "backend": {"file": "backend.json", "sha256": "<64 hex>", "version": "0.2.4", "source_sha": "<40 hex>"}, + "frontend": {"file": "frontend.json", "sha256": "<64 hex>", "version": "0.2.1", "source_sha": "<40 hex>"}, + "mcp": {"file": "mcp.json", "sha256": "<64 hex>", "version": "0.3.0", "source_sha": "<40 hex>"} } ``` -Place referenced bundles inside the plan directory. Verify offline: - ```sh +cargo run --locked -p marreq-contracts -- verify --manifest deployment.json +# Equivalent thin wrapper: scripts/check-api-compatibility.sh --manifest deployment.json ``` -Exit 0 means all checked declarations/contracts are compatible. Exit 1 means -incompatibility, incomplete evidence, invalid metadata, wrong deployment variant, -a missing artifact, or a digest/source/version mismatch. Output is structured JSON -with component-specific diagnostics. The comparator checks request acceptance in -the consumer-to-producer direction and response acceptance in the reverse -direction. This command does not download artifacts, execute plans, send tokens, -publish images or deploy anything. SHA-256 pins content; trusted provenance for -retrieving artifacts remains the release/deployment orchestrator's responsibility. - -### Current tag workflow and future global releases - -`release-artifacts.yml` retains the existing component tag triggers. Every image -publisher depends on contract tests and the mandatory deployment gate. Once P0 -coverage is complete, prepare a separate immutable revision in this repository -containing `deployment.json` and all referenced bundles; set the repository variable -`MARREQ_RELEASE_CONTRACTS_SHA` to that full commit SHA. This revision is prepared -**after** the candidate source commit exists, avoiding self-referential source -hashes in a manifest committed with its own candidate. The workflow checks out -that exact plan revision, exports the candidate contract from the tagged source, -and checks its digest, version and source SHA against the plan. Unchanged -frontend/MCP/backend bundles are never replaced with the tag checkout's versions. -The candidate contract is retained as a workflow artifact before image publishing. -No default production version or fabricated historical manifest is provided. - -The future global-release/selective-deployment pipeline should retrieve trusted -retained bundles, construct every resulting deployment plan, and invoke this -same command plus the reusable `api-contracts.yml` workflow. Global version -registry/tag migration, image digest provenance, actual CD, database migrations, -rollback and VPS configuration remain in #423. Nothing here deploys to the VPS. +For this backend-only update, retain the actual frontend 0.2.1 and MCP 0.3.0 +bundles. The candidate 0.2.4 must satisfy their request/response contracts and each +consumer's backend range. Equal component versions are not required. The backend's +existing frontend acceptance range is also enforced. An unrelated backend route +outside both consumer bundles can change without failing their compatibility. + +Optional request fields and additional response fields are compatible where the +old contract accepts them. Removed guaranteed response properties, new required +request properties, narrowed request enums, widened closed response enums, +incompatible types/authentication, or missing required operations fail. Per-operation +Bearer scopes and stable CSRF policy IDs are checked separately; unused global +security schemes and exact global status/header set equality do not couple APIs. +The pinned standards engine decides ordinary OpenAPI changes, not homegrown rules. +Missing evidence, altered hashes, mismatched source/version, invalid policy, +unsupported semantics and uncertain required operations exit nonzero with JSON +errors. Version ranges supplement executable contracts and never prove them alone. +During rolling upgrades, check every old/new combination that may coexist. + +Current component-tag publishing is retained. A publication job checks out an +immutable plan revision from `MARREQ_RELEASE_CONTRACTS_SHA`, exports the exact tagged +candidate, and verifies its version, source and bundle hash against the plan before +any image publisher runs. The release gate uses the **actual artifact combination**; +it does not require the latest checkout's unchanged consumers. The PR gate instead +checks all calls in current source. No gate requires coverage of unconsumed routes. +No global release migration or production deployment is performed here. + +The PR remains draft until required consumer schema gaps and security failures are +resolved; a document-only check is not a merge/release-readiness assertion. diff --git a/frontend/src/api/__tests__/catalog.test.ts b/frontend/src/api/__tests__/catalog.test.ts index 13efb309..bcac0bec 100644 --- a/frontend/src/api/__tests__/catalog.test.ts +++ b/frontend/src/api/__tests__/catalog.test.ts @@ -157,9 +157,9 @@ describe('catalog API', () => { { name: 'listVerificationMethods', - call: () => listVerificationMethods(), + call: () => listVerificationMethods(5), method: 'GET', - url: '/api/verification-methods', + url: '/api/projects/5/verification-methods', }, { name: 'createVerificationMethod scopes the body to the project', diff --git a/frontend/src/api/catalog.ts b/frontend/src/api/catalog.ts index 8fb00c17..d84a3a45 100644 --- a/frontend/src/api/catalog.ts +++ b/frontend/src/api/catalog.ts @@ -248,8 +248,8 @@ export async function deleteCustomField( }); } -export async function listVerificationMethods(): Promise { - return fetchJson('/api/verification-methods'); +export async function listVerificationMethods(projectId: number): Promise { + return fetchJson(`/api/projects/${projectId}/verification-methods`); } export async function createVerificationMethod( diff --git a/frontend/src/api/generated-contract.ts b/frontend/src/api/generated-contract.ts index 5e16cda6..13b8e14f 100644 --- a/frontend/src/api/generated-contract.ts +++ b/frontend/src/api/generated-contract.ts @@ -1,450 +1,484 @@ -// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml. -// Do not edit; this file carries wire types, not a second contract. - -export type ApiError = { - "status": number; - "error": string; - "message": string; - [key: string]: unknown; -}; - -export type BuildInfo = { - "backend_version": string; - "backend_git_sha": string; - "deployment_mode": "server" | "cloud"; - "frontend_compatibility": { - "min_version": string; - "max_version": string; - [key: string]: unknown; -}; - [key: string]: unknown; -}; - -export type DeploymentInfo = { - "mode": "server" | "cloud"; - "allows_self_registration": boolean; - "requires_email_verification": boolean; - "allows_admin_promotion": boolean; - "assigns_personal_workspace": boolean; - "allows_self_administered_user_creation": boolean; - "allows_database_backup": boolean; - [key: string]: unknown; -}; - -export type McpPrincipalResponse = { - "user_id": number; - "authentication_type": string; - "principal_id": string; - "client_id": (string | null); - "grant_id": (number | null); - [key: string]: unknown; -}; - -export type UseCaseBranch = { - "id": string; - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps"?: Array; -}; - -export type UseCaseBranchEnd = ({ - "type": "rejoin"; - "step": string; -} | { - "type": "end"; - "outcome": "success" | "failure"; -}); - -export type UseCaseBranchResponse = { - "id": string; - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps": Array; -}; - -export type UseCaseDependents = ("cascade" | { - "rebranch_to": string; - [key: string]: unknown; -}); - -export type UseCaseDiff = { - "from": (UseCaseDiffEnd | null); - "to": UseCaseDiffEnd; - "summary": { - "added": number; - "removed": number; - "edited": number; - "moved": number; - "rebranched": number; - [key: string]: unknown; -}; - "header": { - [key: string]: { - "old": (string | null | Array); - "new": (string | null | Array); - [key: string]: unknown; -}; -}; - "elements": Array; - [key: string]: unknown; -}; - -export type UseCaseDiffEnd = { - "version_id": (number | null); - "version_number": (number | null); - "draft_revision": (number | null); - [key: string]: unknown; -}; - -export type UseCaseDiffSide = { - "list": "preconditions" | "main" | "postconditions" | "branch" | "branches"; - "branch_id"?: string; - "label": (string | null); - "text": string; - "from_step"?: string; - "end"?: UseCaseBranchEnd; - "kind"?: "alternative" | "exception"; - [key: string]: unknown; -}; - -export type UseCaseDocument = { - "schema"?: "marreq.use-case.v1"; - "title": string; - "primary_actor"?: (string | null); - "secondary_actors"?: Array; - "summary"?: (string | null); - "preconditions"?: Array; - "main"?: Array; - "branches"?: Array; - "postconditions"?: Array; -}; - -export type UseCaseDocumentResponse = { - "schema": "marreq.use-case.v1"; - "title": string; - "primary_actor": (string | null); - "secondary_actors": Array; - "summary": (string | null); - "preconditions": Array; - "main": Array; - "branches": Array; - "postconditions": Array; -}; - -export type UseCaseEffect = ({ - "type": "relabelled"; - "id": string; - "from": string; - "to": string; - [key: string]: unknown; -} | { - "type": "removed"; - "id": string; - [key: string]: unknown; -} | { - "type": "rebranched"; - "id": string; - "from_step": string; - [key: string]: unknown; -} | { - "type": "created"; - "id": string; - [key: string]: unknown; -}); - -export type UseCaseElement = { - "id": string; - "text": string; -}; - -export type UseCaseElementDiff = { - "id": string; - "kind": "precondition" | "postcondition" | "step" | "branch"; - "changes": Array<"added" | "removed" | "edited" | "moved" | "rebranched">; - "old": (UseCaseDiffSide | null); - "new": (UseCaseDiffSide | null); - "text_diff"?: Array<{ - "op": "equal" | "delete" | "insert"; - "text": string; - [key: string]: unknown; -}>; - [key: string]: unknown; -}; - -export type UseCaseListRef = ("preconditions" | "main" | "postconditions" | { - "branch": string; - [key: string]: unknown; -}); - -export type UseCaseNewBranch = { - "id"?: (string | null); - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps"?: Array; -}; - -export type UseCaseNewElement = { - "id"?: (string | null); - "text": string; -}; - -export type UseCaseOperation = ({ - "op": "set_header"; - "title"?: (string | null); - "primary_actor"?: (string | null); - "secondary_actors"?: (Array | null); - "summary"?: (string | null); -} | { - "op": "add_element"; - "list": UseCaseListRef; - "after"?: (string | null); - "element": UseCaseNewElement; -} | { - "op": "update_text"; - "id": string; - "text": string; -} | { - "op": "move_element"; - "id": string; - "list"?: (UseCaseListRef | null); - "after"?: (string | null); -} | { - "op": "remove_element"; - "id": string; - "dependents"?: (UseCaseDependents | null); -} | { - "op": "add_branch"; - "branch": UseCaseNewBranch; -} | { - "op": "update_branch"; - "id": string; - "title"?: (string | null); - "kind"?: ("alternative" | "exception" | null); - "from_step"?: (string | null); - "end"?: (UseCaseBranchEnd | null); -} | { - "op": "remove_branch"; - "id": string; -}); - -export type UseCasePublishResponse = { - "version": use_cases_VersionView_response; - "removed_links": Array; - [key: string]: unknown; -}; - -export type entities_User_response = { - "id": number; - "username": string; - "name": string; - "email": string; - "creation_date": string; - "last_login": string; - "is_admin": boolean; - "email_verified": boolean; - [key: string]: unknown; -}; - -export type external_ProviderDiscovery_response = { - "password_enabled": boolean; - "external": Array; - [key: string]: unknown; -}; - -export type external_PublicAuthProvider_response = { - "id": string; - "display_name": string; - [key: string]: unknown; -}; - -export type use_cases_BaselineUseCaseView_response = { - "use_case_id": number; - "reference_code": string; - "title": string; - "primary_actor": (string | null); - "version_id": number; - "version_number": number; - "approval_state": string; - "current_version_id": (number | null); - "current_version_number": (number | null); - "link_count": number; - [key: string]: unknown; -}; - -export type use_cases_ClearSuspectResponse_response = { - "status": string; - "cleared": boolean; - [key: string]: unknown; -}; - -export type use_cases_DraftChangeView_response = { - "use_case_id": number; - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - "effects": Array; - [key: string]: unknown; -}; - -export type use_cases_DraftSummary_response = { - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - [key: string]: unknown; -}; - -export type use_cases_DraftView_response = { - "use_case_id": number; - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - [key: string]: unknown; -}; - -export type use_cases_LinkView_response = { - "id": number; - "element_id": (string | null); - "element_label": (string | null); - "target_type": "requirement" | "verification"; - "requirement_id": (number | null); - "verification_id": (number | null); - "target_reference_code": string; - "target_title": string; - "rationale": (string | null); - "suspect": boolean; - "suspect_at": (string | null); - "suspect_reason": (string | null); - "linked_requirement_version_id": (number | null); - "current_requirement_version_id": (number | null); - "created_by": (number | null); - "created_at": string; - [key: string]: unknown; -}; - -export type use_cases_NextCode_response = { - "reference_code": string; - [key: string]: unknown; -}; - -export type use_cases_PutDraftRequest_request = { - "base_revision"?: (number | null); - "document": UseCaseDocument; - [key: string]: unknown; -}; - -export type use_cases_SetApprovalRequest_request = { - "state": string; - [key: string]: unknown; -}; - -export type use_cases_UseCaseCommentView_response = { - "id": number; - "use_case_id": number; - "use_case_version_id": (number | null); - "element_id": (string | null); - "element_label": (string | null); - "author_id": number; - "author_name": string; - "body": string; - "created_at": string; - [key: string]: unknown; -}; - -export type use_cases_UseCaseDetail_response = { - "id": number; - "project_id": number; - "reference_code": string; - "title": string; - "created_by": (number | null); - "created_at": string; - "updated_at": string; - "current_version": (use_cases_VersionView_response | null); - "draft": (use_cases_DraftSummary_response | null); - [key: string]: unknown; -}; - -export type use_cases_UseCaseRef_response = { - "use_case_id": number; - "reference_code": string; - "title": string; - "link_id": number; - "element_id": (string | null); - "element_label": (string | null); - "suspect": boolean; - [key: string]: unknown; -}; - -export type use_cases_UseCaseRow_response = { - "id": number; - "reference_code": string; - "title": string; - "primary_actor": (string | null); - "step_count": number; - "alternative_count": number; - "exception_count": number; - "approval_state": (string | null); - "current_version_number": (number | null); - "has_draft": boolean; - "link_count": number; - "suspect_count": number; - "updated_at": string; - [key: string]: unknown; -}; - -export type use_cases_VersionSummary_response = { - "id": number; - "version_number": number; - "approval_state": string; - "title": string; - "primary_actor": (string | null); - "change_note": (string | null); - "created_by": (number | null); - "created_by_name": (string | null); - "created_at": string; - "reviewed_by": (number | null); - "reviewed_at": (string | null); - "approved_by": (number | null); - "approved_at": (string | null); - [key: string]: unknown; -}; - -export type use_cases_VersionView_response = { - "id": number; - "version_number": number; - "approval_state": string; - "title": string; - "primary_actor": (string | null); - "change_note": (string | null); - "created_by": (number | null); - "created_by_name": (string | null); - "created_at": string; - "reviewed_by": (number | null); - "reviewed_at": (string | null); - "approved_by": (number | null); - "approved_at": (string | null); - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - [key: string]: unknown; -}; +// Generated by scripts/generate-api-types.mjs; do not edit. +export interface components { + schemas: { + ApiError: { + error: string; + message: string; + status: number; + } & { + [key: string]: unknown; + }; + BuildInfo: { + backend_git_sha: string; + backend_version: string; + /** @enum {string} */ + deployment_mode: "server" | "cloud"; + frontend_compatibility: { + max_version: string; + min_version: string; + } & { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + DeploymentInfo: { + allows_admin_promotion: boolean; + allows_database_backup: boolean; + allows_self_administered_user_creation: boolean; + allows_self_registration: boolean; + assigns_personal_workspace: boolean; + /** @enum {string} */ + mode: "server" | "cloud"; + requires_email_verification: boolean; + } & { + [key: string]: unknown; + }; + UseCaseBranch: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id: string; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps?: components["schemas"]["UseCaseElement"][]; + title: string; + }; + UseCaseBranchEnd: { + step: string; + /** @constant */ + type: "rejoin"; + } | { + /** @enum {string} */ + outcome: "success" | "failure"; + /** @constant */ + type: "end"; + }; + UseCaseBranchResponse: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id: string; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps: components["schemas"]["UseCaseElement"][]; + title: string; + }; + UseCaseDependents: "cascade" | ({ + rebranch_to: string; + } & { + [key: string]: unknown; + }); + UseCaseDiff: { + elements: components["schemas"]["UseCaseElementDiff"][]; + from: components["schemas"]["UseCaseDiffEnd"] | null; + header: { + [key: string]: { + new: string | null | string[]; + old: string | null | string[]; + } & { + [key: string]: unknown; + }; + }; + summary: { + added: number; + edited: number; + moved: number; + rebranched: number; + removed: number; + } & { + [key: string]: unknown; + }; + to: components["schemas"]["UseCaseDiffEnd"]; + } & { + [key: string]: unknown; + }; + UseCaseDiffEnd: { + draft_revision: number | null; + version_id: number | null; + version_number: number | null; + } & { + [key: string]: unknown; + }; + UseCaseDiffSide: { + branch_id?: string; + end?: components["schemas"]["UseCaseBranchEnd"]; + from_step?: string; + /** @enum {string} */ + kind?: "alternative" | "exception"; + label: string | null; + /** @enum {string} */ + list: "preconditions" | "main" | "postconditions" | "branch" | "branches"; + text: string; + } & { + [key: string]: unknown; + }; + UseCaseDocument: { + branches?: components["schemas"]["UseCaseBranch"][]; + main?: components["schemas"]["UseCaseElement"][]; + postconditions?: components["schemas"]["UseCaseElement"][]; + preconditions?: components["schemas"]["UseCaseElement"][]; + primary_actor?: string | null; + /** @constant */ + schema?: "marreq.use-case.v1"; + secondary_actors?: string[]; + summary?: string | null; + title: string; + }; + UseCaseDocumentResponse: { + branches: components["schemas"]["UseCaseBranchResponse"][]; + main: components["schemas"]["UseCaseElement"][]; + postconditions: components["schemas"]["UseCaseElement"][]; + preconditions: components["schemas"]["UseCaseElement"][]; + primary_actor: string | null; + /** @constant */ + schema: "marreq.use-case.v1"; + secondary_actors: string[]; + summary: string | null; + title: string; + }; + UseCaseEffect: ({ + from: string; + id: string; + to: string; + /** @constant */ + type: "relabelled"; + } & { + [key: string]: unknown; + }) | ({ + id: string; + /** @constant */ + type: "removed"; + } & { + [key: string]: unknown; + }) | ({ + from_step: string; + id: string; + /** @constant */ + type: "rebranched"; + } & { + [key: string]: unknown; + }) | ({ + id: string; + /** @constant */ + type: "created"; + } & { + [key: string]: unknown; + }); + UseCaseElement: { + id: string; + text: string; + }; + UseCaseElementDiff: { + changes: ("added" | "removed" | "edited" | "moved" | "rebranched")[]; + id: string; + /** @enum {string} */ + kind: "precondition" | "postcondition" | "step" | "branch"; + new: components["schemas"]["UseCaseDiffSide"] | null; + old: components["schemas"]["UseCaseDiffSide"] | null; + text_diff?: ({ + /** @enum {string} */ + op: "equal" | "delete" | "insert"; + text: string; + } & { + [key: string]: unknown; + })[]; + } & { + [key: string]: unknown; + }; + UseCaseListRef: ("preconditions" | "main" | "postconditions") | ({ + branch: string; + } & { + [key: string]: unknown; + }); + UseCaseNewBranch: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id?: string | null; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps?: components["schemas"]["UseCaseNewElement"][]; + title: string; + }; + UseCaseNewElement: { + id?: string | null; + text: string; + }; + UseCaseOperation: { + /** @constant */ + op: "set_header"; + primary_actor?: string | null; + secondary_actors?: string[] | null; + summary?: string | null; + title?: string | null; + } | { + after?: string | null; + element: components["schemas"]["UseCaseNewElement"]; + list: components["schemas"]["UseCaseListRef"]; + /** @constant */ + op: "add_element"; + } | { + id: string; + /** @constant */ + op: "update_text"; + text: string; + } | { + after?: string | null; + id: string; + list?: components["schemas"]["UseCaseListRef"] | null; + /** @constant */ + op: "move_element"; + } | { + dependents?: components["schemas"]["UseCaseDependents"] | null; + id: string; + /** @constant */ + op: "remove_element"; + } | { + branch: components["schemas"]["UseCaseNewBranch"]; + /** @constant */ + op: "add_branch"; + } | { + end?: components["schemas"]["UseCaseBranchEnd"] | null; + from_step?: string | null; + id: string; + kind?: ("alternative" | "exception") | null; + /** @constant */ + op: "update_branch"; + title?: string | null; + } | { + id: string; + /** @constant */ + op: "remove_branch"; + }; + UseCasePublishResponse: { + removed_links: components["schemas"]["use_cases_LinkView_response"][]; + version: components["schemas"]["use_cases_VersionView_response"]; + } & { + [key: string]: unknown; + }; + entities_User_response: { + creation_date: string; + email: string; + email_verified: boolean; + id: number; + is_admin: boolean; + last_login: string; + name: string; + username: string; + } & { + [key: string]: unknown; + }; + external_ProviderDiscovery_response: { + external: components["schemas"]["external_PublicAuthProvider_response"][]; + password_enabled: boolean; + } & { + [key: string]: unknown; + }; + external_PublicAuthProvider_response: { + display_name: string; + id: string; + } & { + [key: string]: unknown; + }; + use_cases_BaselineUseCaseView_response: { + approval_state: string; + current_version_id: number | null; + current_version_number: number | null; + link_count: number; + primary_actor: string | null; + reference_code: string; + title: string; + use_case_id: number; + version_id: number; + version_number: number; + } & { + [key: string]: unknown; + }; + use_cases_ClearSuspectResponse_response: { + cleared: boolean; + status: string; + } & { + [key: string]: unknown; + }; + use_cases_DraftChangeView_response: { + base_version_id: number | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + effects: components["schemas"]["UseCaseEffect"][]; + labels: { + [key: string]: string; + }; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_DraftSummary_response: { + base_version_id: number | null; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + } & { + [key: string]: unknown; + }; + use_cases_DraftView_response: { + base_version_id: number | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + labels: { + [key: string]: string; + }; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_LinkView_response: { + created_at: string; + created_by: number | null; + current_requirement_version_id: number | null; + element_id: string | null; + element_label: string | null; + id: number; + linked_requirement_version_id: number | null; + rationale: string | null; + requirement_id: number | null; + suspect: boolean; + suspect_at: string | null; + suspect_reason: string | null; + target_reference_code: string; + target_title: string; + /** @enum {string} */ + target_type: "requirement" | "verification"; + verification_id: number | null; + } & { + [key: string]: unknown; + }; + use_cases_NextCode_response: { + reference_code: string; + } & { + [key: string]: unknown; + }; + use_cases_PutDraftRequest_request: { + base_revision?: number | null; + document: components["schemas"]["UseCaseDocument"]; + } & { + [key: string]: unknown; + }; + use_cases_SetApprovalRequest_request: { + state: string; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseCommentView_response: { + author_id: number; + author_name: string; + body: string; + created_at: string; + element_id: string | null; + element_label: string | null; + id: number; + use_case_id: number; + use_case_version_id: number | null; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseDetail_response: { + created_at: string; + created_by: number | null; + current_version: components["schemas"]["use_cases_VersionView_response"] | null; + draft: components["schemas"]["use_cases_DraftSummary_response"] | null; + id: number; + project_id: number; + reference_code: string; + title: string; + updated_at: string; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseRef_response: { + element_id: string | null; + element_label: string | null; + link_id: number; + reference_code: string; + suspect: boolean; + title: string; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseRow_response: { + alternative_count: number; + approval_state: string | null; + current_version_number: number | null; + exception_count: number; + has_draft: boolean; + id: number; + link_count: number; + primary_actor: string | null; + reference_code: string; + step_count: number; + suspect_count: number; + title: string; + updated_at: string; + } & { + [key: string]: unknown; + }; + use_cases_VersionSummary_response: { + approval_state: string; + approved_at: string | null; + approved_by: number | null; + change_note: string | null; + created_at: string; + created_by: number | null; + created_by_name: string | null; + id: number; + primary_actor: string | null; + reviewed_at: string | null; + reviewed_by: number | null; + title: string; + version_number: number; + } & { + [key: string]: unknown; + }; + use_cases_VersionView_response: { + approval_state: string; + approved_at: string | null; + approved_by: number | null; + change_note: string | null; + created_at: string; + created_by: number | null; + created_by_name: string | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + id: number; + labels: { + [key: string]: string; + }; + primary_actor: string | null; + reviewed_at: string | null; + reviewed_by: number | null; + title: string; + version_number: number; + } & { + [key: string]: unknown; + }; + }; + responses: { + /** @description API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. */ + ApiError: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + }; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index be9cc5c0..32c8be4f 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -1,3 +1,4 @@ +import type { components as ApiComponents } from './generated-contract'; /** API shapes aligned with marreq-core JSON (snake_case). */ export interface RequirementStatus { @@ -187,9 +188,9 @@ export interface DashboardPayload { csrf_token: string; } -export type DeploymentInfo = import('./generated-contract').DeploymentInfo; +export type DeploymentInfo = ApiComponents['schemas']['DeploymentInfo']; -export type AuthProviderDiscovery = import('./generated-contract').external_ProviderDiscovery_response; +export type AuthProviderDiscovery = ApiComponents['schemas']['external_ProviderDiscovery_response']; export interface ConnectedIdentity { id: number; @@ -217,7 +218,7 @@ export interface FrontendCompatibility { } /** Response from `GET /api/meta/build` (issue #213). */ -export type BuildInfo = import('./generated-contract').BuildInfo; +export type BuildInfo = ApiComponents['schemas']['BuildInfo']; export type RegistrationBody = { username: string; @@ -972,16 +973,16 @@ export interface UseCaseDocument { /** Display labels by element id: `3`, `A3`, `A3.1`, `E4`, `P1`, `Q1`. */ export type UseCaseLabels = Record; -export type UseCaseRow = import('./generated-contract').use_cases_UseCaseRow_response; +export type UseCaseRow = ApiComponents['schemas']['use_cases_UseCaseRow_response']; -export type UseCaseVersionSummary = import('./generated-contract').use_cases_VersionSummary_response; +export type UseCaseVersionSummary = ApiComponents['schemas']['use_cases_VersionSummary_response']; export interface UseCaseVersion extends UseCaseVersionSummary { document: UseCaseDocument; labels: UseCaseLabels; } -export type UseCaseDraftSummary = import('./generated-contract').use_cases_DraftSummary_response; +export type UseCaseDraftSummary = ApiComponents['schemas']['use_cases_DraftSummary_response']; export interface UseCaseDraft extends UseCaseDraftSummary { use_case_id: number; @@ -1043,10 +1044,10 @@ export type UseCaseOperation = | { op: 'remove_branch'; id: string }; /** A link from a use case, or one of its elements, to a requirement or verification (issue #400). */ -export type UseCaseLink = import('./generated-contract').use_cases_LinkView_response; +export type UseCaseLink = ApiComponents['schemas']['use_cases_LinkView_response']; /** A use case that links to a requirement or verification. */ -export type UseCaseRef = import('./generated-contract').use_cases_UseCaseRef_response; +export type UseCaseRef = ApiComponents['schemas']['use_cases_UseCaseRef_response']; /** Structural diff of two use case documents (issue #401). */ export type UseCaseChange = 'added' | 'removed' | 'edited' | 'moved' | 'rebranched'; @@ -1084,7 +1085,7 @@ export interface UseCaseDiff { elements: UseCaseElementDiff[]; } -export type UseCaseComment = import('./generated-contract').use_cases_UseCaseCommentView_response; +export type UseCaseComment = ApiComponents['schemas']['use_cases_UseCaseCommentView_response']; /** A use case version a baseline recorded (issue #402). */ -export type BaselineUseCase = import('./generated-contract').use_cases_BaselineUseCaseView_response; +export type BaselineUseCase = ApiComponents['schemas']['use_cases_BaselineUseCaseView_response']; diff --git a/frontend/src/pages/SystemLogsPage.tsx b/frontend/src/pages/SystemLogsPage.tsx index 82f3f7c7..16a8ee12 100644 --- a/frontend/src/pages/SystemLogsPage.tsx +++ b/frontend/src/pages/SystemLogsPage.tsx @@ -114,7 +114,7 @@ export default function SystemLogsPage() { listVerificationStatuses().catch(() => []), listCategories().catch(() => []), listApplicability().catch(() => []), - listVerificationMethods().catch(() => []), + Promise.all((dashboard?.projects ?? []).map((p) => listVerificationMethods(p.id).catch(() => []))).then((rows) => rows.flat()), ]); setItems(res.items); setTotal(res.total); @@ -131,7 +131,7 @@ export default function SystemLogsPage() { setLoading(false); } }, - [filterParams], + [filterParams, dashboard?.projects], ); useEffect(() => { diff --git a/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx b/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx index 9cc91639..191dde0a 100644 --- a/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx +++ b/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx @@ -159,6 +159,7 @@ describe('ReportBuilderPage', () => { await waitFor(() => expect(screen.getByTestId('where')).toHaveTextContent('?type=vcd')); await userEvent.click(screen.getByRole('checkbox', { name: 'Include Contents' })); + await screen.findByText('Unsaved changes.'); vi.mocked(window.confirm).mockReturnValue(false); await userEvent.selectOptions(screen.getByRole('combobox', { name: 'Report' }), 'coverage'); expect(screen.getByTestId('where')).toHaveTextContent('?type=vcd'); diff --git a/marreq-cloud/Cargo.toml b/marreq-cloud/Cargo.toml index 114fba30..861acbb7 100644 --- a/marreq-cloud/Cargo.toml +++ b/marreq-cloud/Cargo.toml @@ -31,7 +31,7 @@ anyhow = { workspace = true } sha2 = { workspace = true } [dev-dependencies] -regex = { workspace = true } +marreq-contracts = { path = "../tools/marreq-contracts" } marreq-core = { path = "../marreq-core", features = ["test-helpers"] } tokio = { workspace = true, features = ["rt"] } serde_json = { workspace = true } diff --git a/marreq-cloud/tests/api_contract_inventory.rs b/marreq-cloud/tests/api_contract_inventory.rs index a0d6e8c5..d1dbac2a 100644 --- a/marreq-cloud/tests/api_contract_inventory.rs +++ b/marreq-cloud/tests/api_contract_inventory.rs @@ -1,15 +1,14 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -//! Compare the compiled Rocket mount inventory with the OpenAPI-derived artifact. -//! No database or test-double routing: declared_mounts is also used by production. +//! Consumer contracts must correspond to compiled production Rocket mounts. +//! Additional, unconsumed backend routes deliberately do not fail this check. use std::collections::BTreeSet; #[test] -fn compiled_server_and_cloud_routes_match_openapi_inventory() { - let expected: Vec = serde_json::from_str(include_str!( - "../../docs/developer/api-contract-routes.json" - )) - .expect("generated route inventory"); - let parameter = regex::Regex::new(r"<([\w]+)(?:\.\.)?>").unwrap(); +fn consumed_contract_routes_exist_in_server_and_cloud() { + let spec = marreq_contracts::contract::load( + &std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../docs/developer/openapi.yaml"), + ) + .unwrap(); for (variant, extra) in [ ("server", Vec::new()), ("cloud", marreq_cloud::routes::routes()), @@ -18,39 +17,35 @@ fn compiled_server_and_cloud_routes_match_openapi_inventory() { let actual: BTreeSet<_> = [("/api", mounts.api), ("", mounts.root)] .into_iter() .flat_map(|(base, routes)| { - let parameter = ¶meter; - routes.into_iter().filter_map(move |(_, route)| { - let path = route.uri.path().to_owned(); - // Origin root UI and internal CSRF denial routes are not - // HTTP consumer APIs. OAuth routes under root are included. - if base.is_empty() - && !(path.starts_with("/oauth/") - || path.starts_with("/.well-known/") - || path.starts_with("/api/")) - { - return None; - } - let normalized = parameter.replace_all(&path, "{$1}"); - Some((route.method.to_string(), format!("{base}{normalized}"))) + routes.into_iter().map(move |(_, route)| { + ( + route.method.to_string().to_lowercase(), + format!( + "{base}{}", + route.uri.path().replace('<', "{").replace('>', "}") + ), + ) }) }) .collect(); - let documented: BTreeSet<_> = expected - .iter() - .filter(|op| { - op["variants"] - .as_array() - .unwrap() - .iter() - .any(|v| v == variant) - }) - .map(|op| { - ( - op["method"].as_str().unwrap().to_owned(), - op["path"].as_str().unwrap().to_owned(), - ) - }) - .collect(); - assert_eq!(actual, documented, "{variant} route contract drift"); + let documented = marreq_contracts::contract::operations(&spec, Some(variant)).unwrap(); + for key in documented.keys() { + assert!( + actual.contains(&(key.method.clone(), key.path.clone())), + "{variant}: missing compiled {key}" + ); + } + for (key, op) in marreq_contracts::contract::operations(&spec, None).unwrap() { + if op + .get("x-marreq-variants") + .and_then(serde_json::Value::as_array) + .is_some_and(|a| !a.iter().any(|x| x == variant)) + { + assert!( + !actual.contains(&(key.method, key.path)), + "deployment-specific endpoint leaked into {variant}" + ); + } + } } } diff --git a/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs b/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs index 946cb674..6c6d6bb7 100644 --- a/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs +++ b/marreq-core/tests/delegated_oauth_mcp_tools_postgres_integration.rs @@ -169,6 +169,13 @@ async fn delegated_scopes_gate_the_routes_wrapped_by_mcp_tools() { SELECT setval(pg_get_serial_sequence('requirement_version_links','id'), 500);", ) .expect("seed fixtures"); + // Isolated browser-session fixture, also used by the real HTTP smoke test. + let password_hash = + marreq_core::auth::password::hash_password("Contract-only-fixture-58!z").unwrap(); + diesel::sql_query("UPDATE users SET password_hash=$1, email_verified=true WHERE id=1") + .bind::(password_hash) + .execute(&mut conn) + .unwrap(); for (raw, scopes) in [ ("projects", vec!["projects:read"]), diff --git a/mcp-server/Dockerfile b/mcp-server/Dockerfile index 1cafae39..3ff8b9bf 100644 --- a/mcp-server/Dockerfile +++ b/mcp-server/Dockerfile @@ -14,7 +14,6 @@ COPY --from=build --chown=node:node /app/node_modules ./node_modules COPY --from=build --chown=node:node /app/dist ./dist COPY --chown=node:node mcp-server/MARREQ_SKILL.md ./MARREQ_SKILL.md COPY --chown=node:node mcp-server/compatibility.json ./compatibility.json -COPY docs/developer/openapi.yaml /app/contracts/openapi.yaml USER node EXPOSE 3000 HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=3 \ diff --git a/mcp-server/dist/client.js b/mcp-server/dist/client.js index 8ff7f956..35f319ba 100644 --- a/mcp-server/dist/client.js +++ b/mcp-server/dist/client.js @@ -47,12 +47,16 @@ export class MarreqClient { async getRequirement(id) { return this.request(`/api/projects/${this.ctx.projectId}/requirements/${id}`); } - async listRequirements(approvalState, hasTests) { + async listRequirements(approvalState, hasTests, extra = {}) { const params = new URLSearchParams(); if (approvalState != null) params.set("approval_state", approvalState); if (hasTests != null) params.set("has_tests", String(hasTests)); + for (const [key, value] of Object.entries(extra)) { + if (value != null && value !== "") + params.set(key, String(value)); + } const q = params.toString(); return this.request(`/api/projects/${this.ctx.projectId}/requirements${q ? `?${q}` : ""}`); } @@ -273,6 +277,104 @@ export class MarreqClient { body: JSON.stringify(payload), }); } + // ----- Tool gaps (issue #416) ----- + project(path) { + return `/api/projects/${this.ctx.projectId}${path}`; + } + withQuery(path, query) { + const params = new URLSearchParams(); + for (const [key, value] of Object.entries(query)) { + if (value != null && value !== "") + params.set(key, String(value)); + } + const q = params.toString(); + return q ? `${path}?${q}` : path; + } + async listRequirementLinkTypes() { + return this.request(this.project("/requirement-version-links/link-types")); + } + async listRequirementLinks(query = {}) { + return this.request(this.withQuery(this.project("/requirement-version-links"), query)); + } + async createRequirementLink(payload, idempotencyKey) { + return this.request(this.project("/requirement-version-links"), { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify(payload), + }); + } + async deleteRequirementLink(linkId) { + return this.request(this.project(`/requirement-version-links/${linkId}`), { method: "DELETE" }); + } + async deleteUseCaseLink(useCaseId, linkId) { + return this.request(this.useCases(`/${useCaseId}/links/${linkId}`), { method: "DELETE" }); + } + async clearUseCaseLinkSuspect(useCaseId, linkId) { + return this.request(this.useCases(`/${useCaseId}/links/${linkId}/clear_suspect`), { method: "POST" }); + } + async listLinkedUseCases(kind, id) { + return this.request(this.project(`/${kind}s/${id}/use-cases`)); + } + async askRequirements(payload) { + return this.request(this.project("/requirements/ask"), { method: "POST", body: JSON.stringify(payload) }); + } + async listReviewers() { + return this.request(this.project("/reviewers")); + } + async getMyPermissions() { + return this.request(this.project("/me/permissions")); + } + async listSavedViews() { + return this.request(this.project("/saved_views")); + } + async listAttachments(entityType, entityId) { + return this.request(this.withQuery(this.project("/attachments"), { entity_type: entityType, entity_id: entityId })); + } + /** What needs the user's attention across projects (not project-scoped). */ + async getAttention() { + return this.request("/api/home/attention"); + } + async listUseCaseVersions(useCaseId) { + return this.request(this.useCases(`/${useCaseId}/versions`)); + } + async listUseCaseComments(useCaseId, query = {}) { + return this.request(this.withQuery(this.useCases(`/${useCaseId}/comments`), query)); + } + async createUseCaseComment(useCaseId, payload, idempotencyKey) { + return this.request(this.useCases(`/${useCaseId}/comments`), { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify(payload), + }); + } + async discardUseCaseDraft(useCaseId) { + return this.request(this.useCases(`/${useCaseId}/draft`), { method: "DELETE" }); + } + /** Project close-out, or one requirement's when requirementId is given. */ + async getCloseOut(requirementId) { + return this.request(this.project(requirementId != null ? `/requirements/${requirementId}/close_out` : "/close_out")); + } + async getImpactedTests(requirementId) { + return this.request(this.project(`/requirements/${requirementId}/impacted_tests`)); + } + async listVerificationSnapshots(verificationId) { + return this.request(this.project(`/verifications/${verificationId}/snapshots`)); + } + async compareVerificationSnapshots(verificationId, v1, v2) { + return this.request(this.project(`/verifications/${verificationId}/snapshots/${v1}/diff/${v2}`)); + } + async setVerificationField(verificationId, field, value) { + return this.request(this.project(`/verifications/${verificationId}/field`), { + method: "POST", + body: JSON.stringify({ field, value }), + }); + } + async getBaselineContents(baselineId, kind) { + return this.request(this.project(`/baselines/${baselineId}/${kind === "use_cases" ? "use-cases" : "verifications"}`)); + } + async diffBaselineVerificationVsCurrent(baselineId, verificationId) { + return this.request(this.project(`/baselines/${baselineId}/verifications/${verificationId}/diff/current`)); + } get projectId() { return this.ctx.projectId; } diff --git a/mcp-server/dist/gap-tools.js b/mcp-server/dist/gap-tools.js new file mode 100644 index 00000000..7ca6712a --- /dev/null +++ b/mcp-server/dist/gap-tools.js @@ -0,0 +1,276 @@ +/** + * MCP tools added for issue #416 (tool gaps). Each one wraps one project-scoped + * backend route; none is a generic REST passthrough. Modes follow the PDR: + * core = every mode, RX = read_extended and up, DW = draft_write, TW = the + * MARREQ_TRACE_WRITE flag (same gate as put_verification_matrix). + */ +import { randomUUID } from "node:crypto"; +import { z } from "zod"; +import { contextAllowsReadExtended } from "./context.js"; +import { paginate, paginationFields } from "./pagination.js"; +const id = (what) => z.number().int().positive().describe(what); +export function registerGapTools(deps) { + const { server, ctx, client, forProject, projectField, operationKey, withAudit, jsonContent } = deps; + const add = (spec) => { + const shape = { + ...(spec.global ? {} : projectField), + ...spec.shape, + ...(spec.paged ? paginationFields : {}), + }; + server.registerTool(spec.name, { + description: spec.description, + inputSchema: z.object(shape), + ...(spec.destructive ? { annotations: { destructiveHint: true } } : {}), + }, async (args) => { + const toolClient = spec.global ? client : forProject(args?.project_id); + const { idempotency_key: _key, ...audited } = args ?? {}; + const summary = spec.summary ? spec.summary(args ?? {}) : audited; + const out = await withAudit(toolClient, spec.name, JSON.stringify(summary), Boolean(spec.write), () => spec.run(toolClient, args ?? {})); + return { content: [jsonContent(spec.paged ? paginate(out, args ?? {}, 50) : out)] }; + }); + }; + // ----- Core: every mode ----- + add({ + name: "list_linked_use_cases", + description: "Use cases linked to a requirement or to a verification, with the link ids and suspect flags. Give exactly one of requirement_id or verification_id.", + shape: { + requirement_id: id("Requirement id").optional(), + verification_id: id("Verification id").optional(), + }, + paged: true, + run: (c, a) => { + if ((a.requirement_id == null) === (a.verification_id == null)) { + throw new Error("Give exactly one of requirement_id or verification_id"); + } + return a.requirement_id != null + ? c.listLinkedUseCases("requirement", a.requirement_id) + : c.listLinkedUseCases("verification", a.verification_id); + }, + }); + add({ + name: "ask_requirements", + description: "Answer a question from the project's requirements (retrieval plus an LLM call, so it costs time and money; prefer semantic_search_requirements when you only need matches). Fails with a clear error when answer generation is disabled on the server.", + shape: { + query: z.string().min(1), + k: z.number().int().min(1).max(20).optional().describe("Requirements used as context (default 10, max 20)."), + status_filter: id("Only requirements with this status id").optional(), + category_filter: id("Only requirements in this category id").optional(), + applicability_filter: id("Only requirements with this applicability id").optional(), + verification_filter: id("Only requirements with this verification method id").optional(), + }, + summary: (a) => ({ query_length: String(a.query ?? "").length, k: a.k }), + run: (c, a) => { + const { project_id: _p, ...payload } = a; + return c.askRequirements(payload); + }, + }); + add({ + name: "list_use_case_versions", + description: "Version history of a use case: version ids and numbers, approval states and change notes. Read a version with get_use_case(version_id).", + shape: { use_case_id: id("Use case id") }, + paged: true, + run: (c, a) => c.listUseCaseVersions(a.use_case_id), + }); + add({ + name: "get_baseline_contents", + description: "What a baseline recorded besides requirements: its use cases (kind=use_cases) or its verifications (kind=verifications).", + shape: { baseline_id: id("Baseline id"), kind: z.enum(["use_cases", "verifications"]) }, + paged: true, + run: (c, a) => c.getBaselineContents(a.baseline_id, a.kind), + }); + // ----- RX: read_extended and up ----- + if (contextAllowsReadExtended(ctx)) { + add({ + name: "list_requirement_link_types", + description: "Link types allowed between requirement versions (for link_requirements).", + shape: {}, + run: (c) => c.listRequirementLinkTypes(), + }); + add({ + name: "list_requirement_links", + description: "Requirement version links of the project, optionally filtered by source version, target version or link type. Use the link id with unlink_requirements.", + shape: { + source_version_id: id("Source requirement version id").optional(), + target_version_id: id("Target requirement version id").optional(), + link_type: z.string().min(1).optional(), + }, + paged: true, + run: (c, a) => c.listRequirementLinks({ + source_version_id: a.source_version_id, + target_version_id: a.target_version_id, + link_type: a.link_type, + }), + }); + add({ + name: "list_reviewers", + description: "User ids designated as reviewers of the project.", + shape: {}, + run: (c) => c.listReviewers(), + }); + add({ + name: "get_my_permissions", + description: "The authenticated user's effective permissions in the project. Check it before set_approval or other writes that may be refused.", + shape: {}, + run: (c) => c.getMyPermissions(), + }); + add({ + name: "list_saved_views", + description: "Saved requirement views visible to the user. Pass a view id as view_id to list_requirements.", + shape: {}, + paged: true, + run: (c) => c.listSavedViews(), + }); + add({ + name: "list_attachments", + description: "Attachments of one requirement or verification (metadata only; files are not downloaded). Over OAuth it needs both requirements:read and verifications:read.", + shape: { entity_type: z.enum(["requirement", "verification"]), entity_id: id("Requirement or verification id") }, + paged: true, + run: (c, a) => c.listAttachments(a.entity_type, a.entity_id), + }); + add({ + name: "get_attention", + description: "What needs the user's attention across their projects: approvals waiting, drafts to review, suspect links, notifications. Not tied to one project.", + shape: {}, + global: true, + run: (c) => c.getAttention(), + }); + add({ + name: "list_use_case_comments", + description: "Comments on a use case, optionally for one version or one step/condition (element_id).", + shape: { + use_case_id: id("Use case id"), + version_id: id("Only comments on this version").optional(), + element_id: z.string().min(1).optional(), + }, + paged: true, + run: (c, a) => c.listUseCaseComments(a.use_case_id, { version_id: a.version_id, element_id: a.element_id }), + }); + add({ + name: "get_close_out", + description: "Close-out status of the project, or of one requirement when requirement_id is given: what lacks a passing verification.", + shape: { requirement_id: id("Requirement id").optional() }, + run: (c, a) => c.getCloseOut(a.requirement_id), + }); + add({ + name: "impacted_tests", + description: "Verifications linked to a requirement that are marked suspect because the requirement changed. Run it after patch_requirement.", + shape: { requirement_id: id("Requirement id") }, + paged: true, + run: (c, a) => c.getImpactedTests(a.requirement_id), + }); + add({ + name: "list_verification_snapshots", + description: "Chronological snapshots of a verification, rebuilt from the audit log. Compare two with compare_verification_snapshots.", + shape: { verification_id: id("Verification id") }, + paged: true, + run: (c, a) => c.listVerificationSnapshots(a.verification_id), + }); + add({ + name: "compare_verification_snapshots", + description: "Structured diff between two snapshots of a verification (v1 = older, v2 = newer snapshot id).", + shape: { verification_id: id("Verification id"), v1: id("Older snapshot id"), v2: id("Newer snapshot id") }, + run: (c, a) => c.compareVerificationSnapshots(a.verification_id, a.v1, a.v2), + }); + add({ + name: "diff_baseline_verification_vs_current", + description: "Diff of a verification as a baseline recorded it against the verification now.", + shape: { baseline_id: id("Baseline id"), verification_id: id("Verification id") }, + run: (c, a) => c.diffBaselineVerificationVsCurrent(a.baseline_id, a.verification_id), + }); + } + // ----- DW: draft_write ----- + if (ctx.mode === "draft_write") { + add({ + name: "unlink_use_case_element", + description: "Delete one use case link (a requirement or verification linked to the use case or one of its steps). Get link ids from list_use_case_links. Deletes only the link, never the use case, requirement or verification.", + shape: { use_case_id: id("Use case id"), link_id: id("Link id") }, + write: true, + destructive: true, + run: (c, a) => c.deleteUseCaseLink(a.use_case_id, a.link_id), + }); + add({ + name: "clear_use_case_link_suspect", + description: "Clear the suspect flag of a use case link after checking the linked requirement still fits the use case.", + shape: { use_case_id: id("Use case id"), link_id: id("Link id") }, + write: true, + run: (c, a) => c.clearUseCaseLinkSuspect(a.use_case_id, a.link_id), + }); + add({ + name: "create_use_case_comment", + description: "Comment on a use case, on one version (use_case_version_id) or one step/condition (element_id). @username mentions of project members notify them. Retrying with the same idempotency_key does not post twice.", + shape: { + idempotency_key: operationKey, + use_case_id: id("Use case id"), + body: z.string().min(1), + use_case_version_id: id("Version the comment is about").optional(), + element_id: z.string().min(1).optional(), + }, + write: true, + summary: (a) => ({ + use_case_id: a.use_case_id, + body_len: String(a.body ?? "").length, + use_case_version_id: a.use_case_version_id, + element_id: a.element_id, + }), + run: (c, a) => c.createUseCaseComment(a.use_case_id, { body: a.body, use_case_version_id: a.use_case_version_id ?? null, element_id: a.element_id ?? null }, a.idempotency_key ?? randomUUID()), + }); + add({ + name: "discard_use_case_draft", + description: "Discard the shared draft of a use case (unpublished edits are lost; published versions stay). Read the draft first with get_use_case_diff to be sure it is stale.", + shape: { use_case_id: id("Use case id") }, + write: true, + destructive: true, + run: (c, a) => c.discardUseCaseDraft(a.use_case_id), + }); + add({ + name: "set_verification_field", + description: "Change one field of a verification without resending the rest. Fields: name, description, source, status_id, reference_code, parent_id, verification_method_id, author_id, reviewer_id. Status changes keep reviewer enforcement.", + shape: { + verification_id: id("Verification id"), + field: z.enum([ + "name", + "description", + "source", + "status_id", + "reference_code", + "parent_id", + "verification_method_id", + "author_id", + "reviewer_id", + ]), + value: z.string().describe("New value; ids as digits. parent_id: empty or 0 clears it."), + }, + write: true, + run: (c, a) => c.setVerificationField(a.verification_id, a.field, a.value), + }); + } + // ----- TW: MARREQ_TRACE_WRITE ----- + if (ctx.traceWrite) { + add({ + name: "link_requirements", + description: "Link two requirement versions (source derives from / refines / satisfies / depends on / relates to target; see list_requirement_link_types). Both versions must be in the project; cycles are refused. Retrying with the same idempotency_key returns the same link.", + shape: { + idempotency_key: operationKey, + source_version_id: id("Source requirement version id"), + target_version_id: id("Target requirement version id"), + link_type: z.string().min(1), + rationale: z.string().optional(), + }, + write: true, + run: (c, a) => c.createRequirementLink({ + source_version_id: a.source_version_id, + target_version_id: a.target_version_id, + link_type: a.link_type, + rationale: a.rationale ?? null, + }, a.idempotency_key ?? randomUUID()), + }); + add({ + name: "unlink_requirements", + description: "Delete one requirement version link by id (from list_requirement_links). Deletes only the link; baselines taken earlier keep what they recorded.", + shape: { link_id: id("Link id") }, + write: true, + destructive: true, + run: (c, a) => c.deleteRequirementLink(a.link_id), + }); + } +} diff --git a/mcp-server/dist/generated-contract.js b/mcp-server/dist/generated-contract.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/mcp-server/dist/generated-contract.js @@ -0,0 +1 @@ +export {}; diff --git a/mcp-server/dist/index.js b/mcp-server/dist/index.js index f19cf540..4e1efafb 100644 --- a/mcp-server/dist/index.js +++ b/mcp-server/dist/index.js @@ -7,6 +7,10 @@ import { z } from "zod"; import { contextAllowsReadExtended, loadContext } from "./context.js"; import { MarreqAuthenticationError, MarreqClient } from "./client.js"; import { loadTransportConfig, startRemoteServer } from "./remote.js"; +import { registerGapTools } from "./gap-tools.js"; +import { paginate, paginationFields } from "./pagination.js"; +import { registerResources } from "./resources.js"; +import { registerPrompts } from "./prompts.js"; /** Requirement statements and verification descriptions use "Marreq statement Markdown" (marreq-core/src/rich_text.rs). */ const DESCRIPTION_FORMAT = "Verification description in Marreq statement Markdown: paragraphs separated by a blank line, '- ' bulleted and '1. ' numbered lists, **bold**, *italic*, `code`, [label](https://...) links (http/https/mailto only). Max 2000 bytes including markup."; const STATEMENT_FORMAT = "Requirement statement in Marreq statement Markdown: paragraphs separated by a blank line, '- ' bulleted and '1. ' numbered lists, **bold**, *italic*, `code`, [label](https://...) links (http/https/mailto only). Max 2000 bytes including markup."; @@ -129,6 +133,31 @@ export function createMarreqServer(ctx) { edit_use_case_draft: ["use_cases:write"], publish_use_case_draft: ["use_cases:write"], link_use_case_element: ["use_cases:write"], + // Tool gaps (issue #416) + list_requirement_link_types: ["traceability:read"], + list_requirement_links: ["traceability:read"], + link_requirements: ["traceability:write"], + unlink_requirements: ["traceability:write"], + unlink_use_case_element: ["use_cases:write"], + clear_use_case_link_suspect: ["use_cases:write"], + list_linked_use_cases: ["use_cases:read"], + ask_requirements: ["requirements:read"], + list_reviewers: ["projects:read"], + get_my_permissions: ["projects:read"], + list_saved_views: ["requirements:read"], + list_attachments: ["requirements:read", "verifications:read"], + get_attention: ["projects:read"], + list_use_case_versions: ["use_cases:read"], + list_use_case_comments: ["use_cases:read"], + create_use_case_comment: ["use_cases:write"], + discard_use_case_draft: ["use_cases:write"], + get_close_out: ["requirements:read"], + impacted_tests: ["requirements:read", "verifications:read"], + list_verification_snapshots: ["verifications:read"], + compare_verification_snapshots: ["verifications:read"], + set_verification_field: ["verifications:write"], + get_baseline_contents: ["baselines:read"], + diff_baseline_verification_vs_current: ["verifications:read", "baselines:read"], }; const originalRegisterTool = server.registerTool.bind(server); const unsafeRegisterTool = originalRegisterTool; @@ -142,6 +171,8 @@ export function createMarreqServer(ctx) { "create_verification", "create_use_case", "publish_use_case_draft", + "link_requirements", + "create_use_case_comment", ].includes(name); const annotations = { readOnlyHint: !mutating, @@ -172,11 +203,11 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_projects", { description: "List only projects accessible to the authenticated Marreq user.", - inputSchema: z.object({}), + inputSchema: z.object({ ...paginationFields }), annotations: { readOnlyHint: true }, - }, async () => { + }, async (args) => { const out = await withAudit(client, "list_projects", "{}", false, () => client.listProjects()); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, args ?? {}))] }; }); server.registerTool("get_requirement", { description: "Get a requirement by id (project-scoped, with trace summary)", @@ -188,21 +219,35 @@ export function createMarreqServer(ctx) { return { content: [jsonContent(out)] }; }); server.registerTool("list_requirements", { - description: "List requirements in the project with optional filters (approval_state, has_tests)", + description: "List requirements in the project with optional filters (approval_state, has_tests, status_id, category_id, free-text q, a saved view_id from list_saved_views) and sorting (sort_column, sort_dir). Pass limit (and cursor from next_cursor) to page; without them the whole list is returned.", inputSchema: z.object({ ...projectField, filter: z .object({ approval_state: z.enum(["draft", "reviewed", "approved"]).optional(), has_tests: z.boolean().optional(), + status_id: z.number().int().positive().optional(), + category_id: z.number().int().positive().optional(), + q: z.string().optional().describe("Text to find in code, title or statement"), + view_id: z.number().int().positive().optional().describe("Saved view id (list_saved_views)"), }) .optional(), + sort_column: z.string().optional(), + sort_dir: z.enum(["asc", "desc"]).optional(), + ...paginationFields, }), }, async (args) => { const f = args?.filter; const toolClient = forProject(args?.project_id); - const out = await withAudit(toolClient, "list_requirements", JSON.stringify(args ?? {}), false, () => toolClient.listRequirements(f?.approval_state, f?.has_tests)); - return { content: [jsonContent(out)] }; + const out = await withAudit(toolClient, "list_requirements", JSON.stringify(args ?? {}), false, () => toolClient.listRequirements(f?.approval_state, f?.has_tests, { + status_id: f?.status_id, + category_id: f?.category_id, + q: f?.q, + view_id: f?.view_id, + sort_column: args?.sort_column, + sort_dir: args?.sort_dir, + })); + return { content: [jsonContent(paginate(out, args ?? {}))] }; }); server.registerTool("get_versions", { description: "Get version history for a requirement", @@ -311,11 +356,12 @@ export function createMarreqServer(ctx) { q: z.string().optional().describe("Filter on code, title or actor"), approval_state: z.enum(["draft", "reviewed", "approved", "unpublished"]).optional(), has_suspect: z.boolean().optional().describe("Only use cases with suspect links"), + ...paginationFields, }), - }, async ({ project_id, q, approval_state, has_suspect }) => { + }, async ({ project_id, q, approval_state, has_suspect, limit, cursor }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_use_cases", JSON.stringify({ q, approval_state, has_suspect }), false, () => toolClient.listUseCases({ q, approval_state, has_suspect })); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, { limit, cursor }))] }; }); server.registerTool("get_use_case", { description: "Get a use case: its current version (or version_id) as a marreq.use-case.v1 document with computed labels (1, 2, A3, A3.1, E4), and the shared draft (document, labels, revision) when one exists. Element ids such as s_โ€ฆ and b_โ€ฆ are stable; labels are not.", @@ -347,21 +393,21 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_use_case_links", { description: "Requirements and verifications linked to a use case or to one of its steps, with suspect flags (a linked requirement changed since the link was made or cleared).", - inputSchema: z.object({ ...projectField, use_case_id: z.number().int().positive() }), - }, async ({ project_id, use_case_id }) => { + inputSchema: z.object({ ...projectField, use_case_id: z.number().int().positive(), ...paginationFields }), + }, async ({ project_id, use_case_id, limit, cursor }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_use_case_links", JSON.stringify({ use_case_id }), false, () => toolClient.listUseCaseLinks(use_case_id)); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, { limit, cursor }))] }; }); // read_extended / draft_write: extra read tools (catalog, verifications, audit, matrix read, โ€ฆ) if (contextAllowsReadExtended(ctx)) { server.registerTool("list_verifications", { description: "List verifications (tests) in the project. Requires MARREQ_MODE=read_extended or draft_write.", - inputSchema: z.object({ ...projectField }), - }, async ({ project_id }) => { + inputSchema: z.object({ ...projectField, ...paginationFields }), + }, async ({ project_id, limit, cursor }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_verifications", "{}", false, () => toolClient.listVerificationsByProject()); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, { limit, cursor }))] }; }); server.registerTool("get_verification", { description: "Get one verification by id. The record must belong to MARREQ_PROJECT_ID. Requires read_extended or draft_write mode.", @@ -381,11 +427,11 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_baselines", { description: "List baselines for the project (metadata only). Use get_baseline for full snapshot.", - inputSchema: z.object({ ...projectField }), - }, async ({ project_id }) => { + inputSchema: z.object({ ...projectField, ...paginationFields }), + }, async ({ project_id, limit, cursor }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_baselines", "{}", false, () => toolClient.listBaselinesByProject()); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, { limit, cursor }))] }; }); server.registerTool("get_requirement_activity", { description: "Audit log entries for a requirement (create/update history with field summaries).", @@ -411,12 +457,13 @@ export function createMarreqServer(ctx) { ...projectField, requirement_id: z.string(), requirement_version_id: z.number().optional(), + ...paginationFields, }), - }, async ({ project_id, requirement_id, requirement_version_id }) => { + }, async ({ project_id, requirement_id, requirement_version_id, limit, cursor }) => { const id = parseInt(requirement_id, 10); const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_requirement_comments", JSON.stringify({ requirement_id, requirement_version_id }), false, () => toolClient.listRequirementComments(id, requirement_version_id ?? null)); - return { content: [jsonContent(out)] }; + return { content: [jsonContent(paginate(out, { limit, cursor }))] }; }); server.registerTool("get_verification_matrix", { description: "Requirement ids linked to a verification in the traceability matrix (read).", @@ -531,6 +578,7 @@ export function createMarreqServer(ctx) { reviewer_id: z.number().optional(), category_id: z.number().optional(), applicability_id: z.number().optional(), + justification: z.string().optional().describe("Why the requirement exists; an empty string clears it."), custom_fields: z .array(z.object({ field_id: z.number(), value: z.string() })) .optional(), @@ -708,6 +756,18 @@ export function createMarreqServer(ctx) { return { content: [jsonContent(out)] }; }); } + registerGapTools({ + server: server, + ctx, + client, + forProject, + projectField, + operationKey, + withAudit, + jsonContent, + }); + registerResources(server, { ctx, client, withAudit }); + registerPrompts(server); return server; } async function main() { diff --git a/mcp-server/dist/pagination.js b/mcp-server/dist/pagination.js new file mode 100644 index 00000000..3cf3ef59 --- /dev/null +++ b/mcp-server/dist/pagination.js @@ -0,0 +1,49 @@ +import { z } from "zod"; +export const MAX_PAGE_SIZE = 200; +export const DEFAULT_PAGE_SIZE = 50; +/** Optional `limit` and opaque `cursor` inputs shared by list tools. */ +export const paginationFields = { + limit: z + .number() + .int() + .min(1) + .max(MAX_PAGE_SIZE) + .optional() + .describe(`Page size (max ${MAX_PAGE_SIZE}). Pass next_cursor from the previous page as cursor to continue.`), + cursor: z.string().optional().describe("Opaque cursor from a previous page's next_cursor."), +}; +function encodeCursor(offset) { + return Buffer.from(JSON.stringify({ o: offset }), "utf8").toString("base64url"); +} +function decodeCursor(cursor) { + try { + const { o } = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")); + if (typeof o === "number" && Number.isInteger(o) && o >= 0) + return o; + } + catch { + // fall through to the error below + } + throw new Error("Invalid cursor: pass the next_cursor value of a previous page unchanged"); +} +/** + * Slices a list response in the MCP server (the backend still returns the whole + * list). Tools that predate paging keep their full output until the caller sends + * `limit` or `cursor`; new list tools pass `defaultLimit` to page from the start. + * Anything that is not an array is returned as is. + */ +export function paginate(data, args, defaultLimit) { + if (!Array.isArray(data)) + return data; + if (args.limit == null && args.cursor == null && defaultLimit == null) + return data; + const limit = Math.min(args.limit ?? defaultLimit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); + const offset = args.cursor != null ? decodeCursor(args.cursor) : 0; + const end = offset + limit; + const page = { + items: data.slice(offset, end), + total: data.length, + next_cursor: end < data.length ? encodeCursor(end) : null, + }; + return page; +} diff --git a/mcp-server/dist/prompts.js b/mcp-server/dist/prompts.js new file mode 100644 index 00000000..8b6752a0 --- /dev/null +++ b/mcp-server/dist/prompts.js @@ -0,0 +1,52 @@ +/** + * MCP prompts (issue #416). Each prompt names the tools it expects, so a client + * can plan the calls. They only describe work; no prompt writes anything. + */ +import { z } from "zod"; +const projectArg = z.string().regex(/^\d+$/).describe("Project id from list_projects"); +const requirementArg = z.string().regex(/^\d+$/).describe("Requirement id"); +function user(text) { + return { messages: [{ role: "user", content: { type: "text", text } }] }; +} +export const PROMPT_NAMES = [ + "review_requirement", + "derive_children", + "coverage_gap_analysis", + "use_case_from_requirement", +]; +export function registerPrompts(server) { + server.registerPrompt("review_requirement", { + title: "Review a requirement", + description: "Check one requirement for ambiguity, testability, duplicates, traceability and suspect links.", + argsSchema: { project_id: projectArg, requirement_id: requirementArg }, + }, ({ project_id, requirement_id }) => user(`Review requirement ${requirement_id} in project ${project_id}; do not change anything.\n` + + `Tools: get_requirement, get_versions, trace_up, trace_down, get_close_out (with requirement_id), ` + + `list_linked_use_cases, semantic_search_requirements.\n` + + `Report: is it atomic, unambiguous and testable; does a similar requirement already exist; does it have a parent ` + + `and a passing verification; are any links suspect. Give findings first, then proposed edits for me to approve.`)); + server.registerPrompt("derive_children", { + title: "Derive child requirements", + description: "Propose atomic child requirements for a parent, without creating them.", + argsSchema: { project_id: projectArg, requirement_id: requirementArg }, + }, ({ project_id, requirement_id }) => user(`Propose child requirements for requirement ${requirement_id} in project ${project_id}.\n` + + `Tools: get_requirement, list_project_catalog, semantic_search_requirements, list_requirement_link_types.\n` + + `Write each child as one atomic, testable statement, check it against existing requirements for overlap, ` + + `and say which link type ties it to the parent. List them for my approval; create them with ` + + `create_requirement (parent_links) only after I say so.`)); + server.registerPrompt("coverage_gap_analysis", { + title: "Coverage gap analysis", + description: "Find requirements without verification and tests made stale by changes.", + argsSchema: { project_id: projectArg }, + }, ({ project_id }) => user(`Analyse verification coverage in project ${project_id}.\n` + + `Tools: coverage_report, get_close_out, list_requirements (has_tests=false), impacted_tests, get_verification_matrix.\n` + + `Rank the gaps by approval state and importance, list suspect tests, and suggest which verification would close ` + + `each gap. Do not link or create anything until I choose.`)); + server.registerPrompt("use_case_from_requirement", { + title: "Use case from a requirement", + description: "Draft a use case that exercises a requirement, and link it.", + argsSchema: { project_id: projectArg, requirement_id: requirementArg }, + }, ({ project_id, requirement_id }) => user(`Draft a use case for requirement ${requirement_id} in project ${project_id}.\n` + + `Tools: get_requirement, list_use_cases, create_use_case, edit_use_case_draft, link_use_case_element.\n` + + `Check list_use_cases for an existing one first. Outline the actor, preconditions, main steps and alternatives, ` + + `show me the outline, then create the draft and link it to the requirement. Publish only if I ask.`)); +} diff --git a/mcp-server/dist/resources.js b/mcp-server/dist/resources.js new file mode 100644 index 00000000..7b41c7fd --- /dev/null +++ b/mcp-server/dist/resources.js @@ -0,0 +1,49 @@ +/** + * MCP resources (issue #416): requirements, use cases and the catalog as + * addressable documents, and the workflow guide. Each read reuses the client + * method and backend scope of the matching tool. + */ +import { readFile } from "node:fs/promises"; +import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +const JSON_TYPE = "application/json"; +function one(value) { + return Array.isArray(value) ? value[0] ?? "" : value ?? ""; +} +function positiveInt(raw, what) { + const n = Number(raw); + if (!Number.isInteger(n) || n <= 0) + throw new Error(`Invalid ${what} in resource URI: ${raw}`); + return n; +} +async function guideText() { + try { + return await readFile(new URL("../MARREQ_SKILL.md", import.meta.url), "utf8"); + } + catch { + return "The Marreq workflow guide (MARREQ_SKILL.md) is not available in this deployment."; + } +} +export function registerResources(server, { client, withAudit }) { + const json = (uri, data) => ({ + contents: [{ uri: uri.href, mimeType: JSON_TYPE, text: JSON.stringify(data, null, 2) }], + }); + // The project in the URI is passed through; the backend still checks that the + // caller has the scope and project permission. + const read = async (uri, vars, name, fn) => { + const projectClient = client.withProject(positiveInt(one(vars.project_id), "project id")); + const out = await withAudit(projectClient, `resource:${name}`, JSON.stringify({ uri: uri.href }), false, () => fn(projectClient)); + return json(uri, out); + }; + server.registerResource("requirement", new ResourceTemplate("marreq://projects/{project_id}/requirements/{id}", { list: undefined }), { title: "Requirement", description: "One requirement with its trace summary.", mimeType: JSON_TYPE }, (uri, vars) => read(uri, vars, "requirement", (c) => c.getRequirement(positiveInt(one(vars.id), "requirement id")))); + server.registerResource("use-case", new ResourceTemplate("marreq://projects/{project_id}/use-cases/{id}", { list: undefined }), { title: "Use case", description: "The current version of one use case.", mimeType: JSON_TYPE }, (uri, vars) => read(uri, vars, "use-case", (c) => c.getUseCase(positiveInt(one(vars.id), "use case id")))); + server.registerResource("catalog", new ResourceTemplate("marreq://projects/{project_id}/catalog", { list: undefined }), { + title: "Project catalog", + description: "Statuses, categories, applicability, verification methods and users of the project.", + mimeType: JSON_TYPE, + }, (uri, vars) => read(uri, vars, "catalog", (c) => c.listProjectCatalog())); + server.registerResource("guide", "marreq://guide", { + title: "Marreq workflow guide", + description: "How to review, create and modify requirements and use cases with the Marreq tools.", + mimeType: "text/markdown", + }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text: await guideText() }] })); +} diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index 28094b84..3c59f108 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -228,9 +228,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -249,9 +246,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -270,9 +264,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -291,9 +282,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -312,9 +300,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -333,9 +318,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -954,17 +936,17 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^2.1.0", "debug": "^4.4.3", "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", - "qs": "^6.15.2", + "qs": "^6.16.1", "raw-body": "^3.0.2", "type-is": "^2.1.0" }, @@ -1326,9 +1308,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -1515,9 +1497,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1535,9 +1517,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ip-address": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", - "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "version": "10.7.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz", + "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==", "engines": { "node": ">= 12" } @@ -1727,9 +1709,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1752,9 +1731,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1777,9 +1753,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -1802,9 +1775,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2110,9 +2080,9 @@ } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.0.tgz", + "integrity": "sha512-5LK5BZ2XDHMvcUJaf0CQ5HL3aVgKbpklvByC7qLSxYkfE8COEEbJg5xCHWCTqzqi0Gs4Jn5Mv5v3R38W6edscA==", "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" diff --git a/mcp-server/src/client.ts b/mcp-server/src/client.ts index 6e083172..cb3b831d 100644 --- a/mcp-server/src/client.ts +++ b/mcp-server/src/client.ts @@ -1,10 +1,9 @@ -import type { - McpPrincipalResponse, - use_cases_UseCaseRow_response as UseCaseRow, - use_cases_UseCaseDetail_response as UseCaseDetail, - use_cases_DraftView_response as UseCaseDraft, - use_cases_DraftChangeView_response as UseCaseDraftChange, -} from "./generated-contract.js"; +import type { components } from "./generated-contract.js"; +type McpPrincipalResponse = components["schemas"]["McpPrincipalResponse"]; +type UseCaseRow = components["schemas"]["use_cases_UseCaseRow_response"]; +type UseCaseDetail = components["schemas"]["use_cases_UseCaseDetail_response"]; +type UseCaseDraft = components["schemas"]["use_cases_DraftView_response"]; +type UseCaseDraftChange = components["schemas"]["use_cases_DraftChangeView_response"]; import type { SessionContext } from "./context.js"; import { currentBearer } from "./credentials.js"; diff --git a/mcp-server/src/generated-contract.ts b/mcp-server/src/generated-contract.ts index 5e16cda6..eb559813 100644 --- a/mcp-server/src/generated-contract.ts +++ b/mcp-server/src/generated-contract.ts @@ -1,450 +1,438 @@ -// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml. -// Do not edit; this file carries wire types, not a second contract. - -export type ApiError = { - "status": number; - "error": string; - "message": string; - [key: string]: unknown; -}; - -export type BuildInfo = { - "backend_version": string; - "backend_git_sha": string; - "deployment_mode": "server" | "cloud"; - "frontend_compatibility": { - "min_version": string; - "max_version": string; - [key: string]: unknown; -}; - [key: string]: unknown; -}; - -export type DeploymentInfo = { - "mode": "server" | "cloud"; - "allows_self_registration": boolean; - "requires_email_verification": boolean; - "allows_admin_promotion": boolean; - "assigns_personal_workspace": boolean; - "allows_self_administered_user_creation": boolean; - "allows_database_backup": boolean; - [key: string]: unknown; -}; - -export type McpPrincipalResponse = { - "user_id": number; - "authentication_type": string; - "principal_id": string; - "client_id": (string | null); - "grant_id": (number | null); - [key: string]: unknown; -}; - -export type UseCaseBranch = { - "id": string; - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps"?: Array; -}; - -export type UseCaseBranchEnd = ({ - "type": "rejoin"; - "step": string; -} | { - "type": "end"; - "outcome": "success" | "failure"; -}); - -export type UseCaseBranchResponse = { - "id": string; - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps": Array; -}; - -export type UseCaseDependents = ("cascade" | { - "rebranch_to": string; - [key: string]: unknown; -}); - -export type UseCaseDiff = { - "from": (UseCaseDiffEnd | null); - "to": UseCaseDiffEnd; - "summary": { - "added": number; - "removed": number; - "edited": number; - "moved": number; - "rebranched": number; - [key: string]: unknown; -}; - "header": { - [key: string]: { - "old": (string | null | Array); - "new": (string | null | Array); - [key: string]: unknown; -}; -}; - "elements": Array; - [key: string]: unknown; -}; - -export type UseCaseDiffEnd = { - "version_id": (number | null); - "version_number": (number | null); - "draft_revision": (number | null); - [key: string]: unknown; -}; - -export type UseCaseDiffSide = { - "list": "preconditions" | "main" | "postconditions" | "branch" | "branches"; - "branch_id"?: string; - "label": (string | null); - "text": string; - "from_step"?: string; - "end"?: UseCaseBranchEnd; - "kind"?: "alternative" | "exception"; - [key: string]: unknown; -}; - -export type UseCaseDocument = { - "schema"?: "marreq.use-case.v1"; - "title": string; - "primary_actor"?: (string | null); - "secondary_actors"?: Array; - "summary"?: (string | null); - "preconditions"?: Array; - "main"?: Array; - "branches"?: Array; - "postconditions"?: Array; -}; - -export type UseCaseDocumentResponse = { - "schema": "marreq.use-case.v1"; - "title": string; - "primary_actor": (string | null); - "secondary_actors": Array; - "summary": (string | null); - "preconditions": Array; - "main": Array; - "branches": Array; - "postconditions": Array; -}; - -export type UseCaseEffect = ({ - "type": "relabelled"; - "id": string; - "from": string; - "to": string; - [key: string]: unknown; -} | { - "type": "removed"; - "id": string; - [key: string]: unknown; -} | { - "type": "rebranched"; - "id": string; - "from_step": string; - [key: string]: unknown; -} | { - "type": "created"; - "id": string; - [key: string]: unknown; -}); - -export type UseCaseElement = { - "id": string; - "text": string; -}; - -export type UseCaseElementDiff = { - "id": string; - "kind": "precondition" | "postcondition" | "step" | "branch"; - "changes": Array<"added" | "removed" | "edited" | "moved" | "rebranched">; - "old": (UseCaseDiffSide | null); - "new": (UseCaseDiffSide | null); - "text_diff"?: Array<{ - "op": "equal" | "delete" | "insert"; - "text": string; - [key: string]: unknown; -}>; - [key: string]: unknown; -}; - -export type UseCaseListRef = ("preconditions" | "main" | "postconditions" | { - "branch": string; - [key: string]: unknown; -}); - -export type UseCaseNewBranch = { - "id"?: (string | null); - "kind": "alternative" | "exception"; - "title": string; - "from_step": string; - "end": UseCaseBranchEnd; - "steps"?: Array; -}; - -export type UseCaseNewElement = { - "id"?: (string | null); - "text": string; -}; - -export type UseCaseOperation = ({ - "op": "set_header"; - "title"?: (string | null); - "primary_actor"?: (string | null); - "secondary_actors"?: (Array | null); - "summary"?: (string | null); -} | { - "op": "add_element"; - "list": UseCaseListRef; - "after"?: (string | null); - "element": UseCaseNewElement; -} | { - "op": "update_text"; - "id": string; - "text": string; -} | { - "op": "move_element"; - "id": string; - "list"?: (UseCaseListRef | null); - "after"?: (string | null); -} | { - "op": "remove_element"; - "id": string; - "dependents"?: (UseCaseDependents | null); -} | { - "op": "add_branch"; - "branch": UseCaseNewBranch; -} | { - "op": "update_branch"; - "id": string; - "title"?: (string | null); - "kind"?: ("alternative" | "exception" | null); - "from_step"?: (string | null); - "end"?: (UseCaseBranchEnd | null); -} | { - "op": "remove_branch"; - "id": string; -}); - -export type UseCasePublishResponse = { - "version": use_cases_VersionView_response; - "removed_links": Array; - [key: string]: unknown; -}; - -export type entities_User_response = { - "id": number; - "username": string; - "name": string; - "email": string; - "creation_date": string; - "last_login": string; - "is_admin": boolean; - "email_verified": boolean; - [key: string]: unknown; -}; - -export type external_ProviderDiscovery_response = { - "password_enabled": boolean; - "external": Array; - [key: string]: unknown; -}; - -export type external_PublicAuthProvider_response = { - "id": string; - "display_name": string; - [key: string]: unknown; -}; - -export type use_cases_BaselineUseCaseView_response = { - "use_case_id": number; - "reference_code": string; - "title": string; - "primary_actor": (string | null); - "version_id": number; - "version_number": number; - "approval_state": string; - "current_version_id": (number | null); - "current_version_number": (number | null); - "link_count": number; - [key: string]: unknown; -}; - -export type use_cases_ClearSuspectResponse_response = { - "status": string; - "cleared": boolean; - [key: string]: unknown; -}; - -export type use_cases_DraftChangeView_response = { - "use_case_id": number; - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - "effects": Array; - [key: string]: unknown; -}; - -export type use_cases_DraftSummary_response = { - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - [key: string]: unknown; -}; - -export type use_cases_DraftView_response = { - "use_case_id": number; - "revision": number; - "base_version_id": (number | null); - "updated_by": (number | null); - "updated_by_name": (string | null); - "updated_at": string; - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - [key: string]: unknown; -}; - -export type use_cases_LinkView_response = { - "id": number; - "element_id": (string | null); - "element_label": (string | null); - "target_type": "requirement" | "verification"; - "requirement_id": (number | null); - "verification_id": (number | null); - "target_reference_code": string; - "target_title": string; - "rationale": (string | null); - "suspect": boolean; - "suspect_at": (string | null); - "suspect_reason": (string | null); - "linked_requirement_version_id": (number | null); - "current_requirement_version_id": (number | null); - "created_by": (number | null); - "created_at": string; - [key: string]: unknown; -}; - -export type use_cases_NextCode_response = { - "reference_code": string; - [key: string]: unknown; -}; - -export type use_cases_PutDraftRequest_request = { - "base_revision"?: (number | null); - "document": UseCaseDocument; - [key: string]: unknown; -}; - -export type use_cases_SetApprovalRequest_request = { - "state": string; - [key: string]: unknown; -}; - -export type use_cases_UseCaseCommentView_response = { - "id": number; - "use_case_id": number; - "use_case_version_id": (number | null); - "element_id": (string | null); - "element_label": (string | null); - "author_id": number; - "author_name": string; - "body": string; - "created_at": string; - [key: string]: unknown; -}; - -export type use_cases_UseCaseDetail_response = { - "id": number; - "project_id": number; - "reference_code": string; - "title": string; - "created_by": (number | null); - "created_at": string; - "updated_at": string; - "current_version": (use_cases_VersionView_response | null); - "draft": (use_cases_DraftSummary_response | null); - [key: string]: unknown; -}; - -export type use_cases_UseCaseRef_response = { - "use_case_id": number; - "reference_code": string; - "title": string; - "link_id": number; - "element_id": (string | null); - "element_label": (string | null); - "suspect": boolean; - [key: string]: unknown; -}; - -export type use_cases_UseCaseRow_response = { - "id": number; - "reference_code": string; - "title": string; - "primary_actor": (string | null); - "step_count": number; - "alternative_count": number; - "exception_count": number; - "approval_state": (string | null); - "current_version_number": (number | null); - "has_draft": boolean; - "link_count": number; - "suspect_count": number; - "updated_at": string; - [key: string]: unknown; -}; - -export type use_cases_VersionSummary_response = { - "id": number; - "version_number": number; - "approval_state": string; - "title": string; - "primary_actor": (string | null); - "change_note": (string | null); - "created_by": (number | null); - "created_by_name": (string | null); - "created_at": string; - "reviewed_by": (number | null); - "reviewed_at": (string | null); - "approved_by": (number | null); - "approved_at": (string | null); - [key: string]: unknown; -}; - -export type use_cases_VersionView_response = { - "id": number; - "version_number": number; - "approval_state": string; - "title": string; - "primary_actor": (string | null); - "change_note": (string | null); - "created_by": (number | null); - "created_by_name": (string | null); - "created_at": string; - "reviewed_by": (number | null); - "reviewed_at": (string | null); - "approved_by": (number | null); - "approved_at": (string | null); - "document": UseCaseDocumentResponse; - "labels": { - [key: string]: string; -}; - [key: string]: unknown; -}; +// Generated by scripts/generate-api-types.mjs; do not edit. +export interface components { + schemas: { + ApiError: { + error: string; + message: string; + status: number; + } & { + [key: string]: unknown; + }; + McpPrincipalResponse: { + authentication_type: string; + client_id: string | null; + grant_id: number | null; + principal_id: string; + user_id: number; + } & { + [key: string]: unknown; + }; + UseCaseBranch: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id: string; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps?: components["schemas"]["UseCaseElement"][]; + title: string; + }; + UseCaseBranchEnd: { + step: string; + /** @constant */ + type: "rejoin"; + } | { + /** @enum {string} */ + outcome: "success" | "failure"; + /** @constant */ + type: "end"; + }; + UseCaseBranchResponse: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id: string; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps: components["schemas"]["UseCaseElement"][]; + title: string; + }; + UseCaseDependents: "cascade" | ({ + rebranch_to: string; + } & { + [key: string]: unknown; + }); + UseCaseDiff: { + elements: components["schemas"]["UseCaseElementDiff"][]; + from: components["schemas"]["UseCaseDiffEnd"] | null; + header: { + [key: string]: { + new: string | null | string[]; + old: string | null | string[]; + } & { + [key: string]: unknown; + }; + }; + summary: { + added: number; + edited: number; + moved: number; + rebranched: number; + removed: number; + } & { + [key: string]: unknown; + }; + to: components["schemas"]["UseCaseDiffEnd"]; + } & { + [key: string]: unknown; + }; + UseCaseDiffEnd: { + draft_revision: number | null; + version_id: number | null; + version_number: number | null; + } & { + [key: string]: unknown; + }; + UseCaseDiffSide: { + branch_id?: string; + end?: components["schemas"]["UseCaseBranchEnd"]; + from_step?: string; + /** @enum {string} */ + kind?: "alternative" | "exception"; + label: string | null; + /** @enum {string} */ + list: "preconditions" | "main" | "postconditions" | "branch" | "branches"; + text: string; + } & { + [key: string]: unknown; + }; + UseCaseDocument: { + branches?: components["schemas"]["UseCaseBranch"][]; + main?: components["schemas"]["UseCaseElement"][]; + postconditions?: components["schemas"]["UseCaseElement"][]; + preconditions?: components["schemas"]["UseCaseElement"][]; + primary_actor?: string | null; + /** @constant */ + schema?: "marreq.use-case.v1"; + secondary_actors?: string[]; + summary?: string | null; + title: string; + }; + UseCaseDocumentResponse: { + branches: components["schemas"]["UseCaseBranchResponse"][]; + main: components["schemas"]["UseCaseElement"][]; + postconditions: components["schemas"]["UseCaseElement"][]; + preconditions: components["schemas"]["UseCaseElement"][]; + primary_actor: string | null; + /** @constant */ + schema: "marreq.use-case.v1"; + secondary_actors: string[]; + summary: string | null; + title: string; + }; + UseCaseEffect: ({ + from: string; + id: string; + to: string; + /** @constant */ + type: "relabelled"; + } & { + [key: string]: unknown; + }) | ({ + id: string; + /** @constant */ + type: "removed"; + } & { + [key: string]: unknown; + }) | ({ + from_step: string; + id: string; + /** @constant */ + type: "rebranched"; + } & { + [key: string]: unknown; + }) | ({ + id: string; + /** @constant */ + type: "created"; + } & { + [key: string]: unknown; + }); + UseCaseElement: { + id: string; + text: string; + }; + UseCaseElementDiff: { + changes: ("added" | "removed" | "edited" | "moved" | "rebranched")[]; + id: string; + /** @enum {string} */ + kind: "precondition" | "postcondition" | "step" | "branch"; + new: components["schemas"]["UseCaseDiffSide"] | null; + old: components["schemas"]["UseCaseDiffSide"] | null; + text_diff?: ({ + /** @enum {string} */ + op: "equal" | "delete" | "insert"; + text: string; + } & { + [key: string]: unknown; + })[]; + } & { + [key: string]: unknown; + }; + UseCaseListRef: ("preconditions" | "main" | "postconditions") | ({ + branch: string; + } & { + [key: string]: unknown; + }); + UseCaseNewBranch: { + end: components["schemas"]["UseCaseBranchEnd"]; + from_step: string; + id?: string | null; + /** @enum {string} */ + kind: "alternative" | "exception"; + steps?: components["schemas"]["UseCaseNewElement"][]; + title: string; + }; + UseCaseNewElement: { + id?: string | null; + text: string; + }; + UseCaseOperation: { + /** @constant */ + op: "set_header"; + primary_actor?: string | null; + secondary_actors?: string[] | null; + summary?: string | null; + title?: string | null; + } | { + after?: string | null; + element: components["schemas"]["UseCaseNewElement"]; + list: components["schemas"]["UseCaseListRef"]; + /** @constant */ + op: "add_element"; + } | { + id: string; + /** @constant */ + op: "update_text"; + text: string; + } | { + after?: string | null; + id: string; + list?: components["schemas"]["UseCaseListRef"] | null; + /** @constant */ + op: "move_element"; + } | { + dependents?: components["schemas"]["UseCaseDependents"] | null; + id: string; + /** @constant */ + op: "remove_element"; + } | { + branch: components["schemas"]["UseCaseNewBranch"]; + /** @constant */ + op: "add_branch"; + } | { + end?: components["schemas"]["UseCaseBranchEnd"] | null; + from_step?: string | null; + id: string; + kind?: ("alternative" | "exception") | null; + /** @constant */ + op: "update_branch"; + title?: string | null; + } | { + id: string; + /** @constant */ + op: "remove_branch"; + }; + UseCasePublishResponse: { + removed_links: components["schemas"]["use_cases_LinkView_response"][]; + version: components["schemas"]["use_cases_VersionView_response"]; + } & { + [key: string]: unknown; + }; + use_cases_BaselineUseCaseView_response: { + approval_state: string; + current_version_id: number | null; + current_version_number: number | null; + link_count: number; + primary_actor: string | null; + reference_code: string; + title: string; + use_case_id: number; + version_id: number; + version_number: number; + } & { + [key: string]: unknown; + }; + use_cases_ClearSuspectResponse_response: { + cleared: boolean; + status: string; + } & { + [key: string]: unknown; + }; + use_cases_DraftChangeView_response: { + base_version_id: number | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + effects: components["schemas"]["UseCaseEffect"][]; + labels: { + [key: string]: string; + }; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_DraftSummary_response: { + base_version_id: number | null; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + } & { + [key: string]: unknown; + }; + use_cases_DraftView_response: { + base_version_id: number | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + labels: { + [key: string]: string; + }; + revision: number; + updated_at: string; + updated_by: number | null; + updated_by_name: string | null; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_LinkView_response: { + created_at: string; + created_by: number | null; + current_requirement_version_id: number | null; + element_id: string | null; + element_label: string | null; + id: number; + linked_requirement_version_id: number | null; + rationale: string | null; + requirement_id: number | null; + suspect: boolean; + suspect_at: string | null; + suspect_reason: string | null; + target_reference_code: string; + target_title: string; + /** @enum {string} */ + target_type: "requirement" | "verification"; + verification_id: number | null; + } & { + [key: string]: unknown; + }; + use_cases_NextCode_response: { + reference_code: string; + } & { + [key: string]: unknown; + }; + use_cases_PutDraftRequest_request: { + base_revision?: number | null; + document: components["schemas"]["UseCaseDocument"]; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseCommentView_response: { + author_id: number; + author_name: string; + body: string; + created_at: string; + element_id: string | null; + element_label: string | null; + id: number; + use_case_id: number; + use_case_version_id: number | null; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseDetail_response: { + created_at: string; + created_by: number | null; + current_version: components["schemas"]["use_cases_VersionView_response"] | null; + draft: components["schemas"]["use_cases_DraftSummary_response"] | null; + id: number; + project_id: number; + reference_code: string; + title: string; + updated_at: string; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseRef_response: { + element_id: string | null; + element_label: string | null; + link_id: number; + reference_code: string; + suspect: boolean; + title: string; + use_case_id: number; + } & { + [key: string]: unknown; + }; + use_cases_UseCaseRow_response: { + alternative_count: number; + approval_state: string | null; + current_version_number: number | null; + exception_count: number; + has_draft: boolean; + id: number; + link_count: number; + primary_actor: string | null; + reference_code: string; + step_count: number; + suspect_count: number; + title: string; + updated_at: string; + } & { + [key: string]: unknown; + }; + use_cases_VersionSummary_response: { + approval_state: string; + approved_at: string | null; + approved_by: number | null; + change_note: string | null; + created_at: string; + created_by: number | null; + created_by_name: string | null; + id: number; + primary_actor: string | null; + reviewed_at: string | null; + reviewed_by: number | null; + title: string; + version_number: number; + } & { + [key: string]: unknown; + }; + use_cases_VersionView_response: { + approval_state: string; + approved_at: string | null; + approved_by: number | null; + change_note: string | null; + created_at: string; + created_by: number | null; + created_by_name: string | null; + document: components["schemas"]["UseCaseDocumentResponse"]; + id: number; + labels: { + [key: string]: string; + }; + primary_actor: string | null; + reviewed_at: string | null; + reviewed_by: number | null; + title: string; + version_number: number; + } & { + [key: string]: unknown; + }; + }; + responses: { + /** @description API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. */ + ApiError: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + }; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} diff --git a/mcp-server/src/remote.ts b/mcp-server/src/remote.ts index 4d517c49..40ff9138 100644 --- a/mcp-server/src/remote.ts +++ b/mcp-server/src/remote.ts @@ -1,4 +1,4 @@ -import type { McpPrincipalResponse } from "./generated-contract.js"; +import type { components } from "./generated-contract.js"; import { randomUUID } from "node:crypto"; import type { Server } from "node:http"; import type { Request, Response } from "express"; @@ -10,7 +10,7 @@ import { loadContext, type SessionContext } from "./context.js"; import { MarreqAuthenticationError, MarreqClient } from "./client.js"; import { withBearer } from "./credentials.js"; -type Principal = McpPrincipalResponse; +type Principal = components["schemas"]["McpPrincipalResponse"]; type PrincipalResolution = | { kind: "ok"; principal: Principal } diff --git a/package-lock.json b/package-lock.json index 4372a619..43e773a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "commitizen": "^4.3.2", "cz-conventional-changelog": "^3.3.0", "happy-dom": "^20.14.6", + "openapi-typescript": "7.13.0", "playwright": "^1.64.1", "purgecss": "^8.0.0", "stylelint": "^17.16.0", @@ -513,6 +514,70 @@ "dev": true, "license": "MIT" }, + "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, + "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/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.22", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.22.tgz", + "integrity": "sha512-dEyx4uHh6Pc6ZkdU9Xe8rA4h12Odq6QKU/fqseBn07xwVwskhHiF3KHhqmw5BR+jAiFTvKTmwCafFql/k1pSzA==", + "dev": true, + "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.3.2", + "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/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz", @@ -629,9 +694,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -650,9 +712,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -671,9 +730,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -692,9 +748,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -713,9 +766,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -734,9 +784,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1047,6 +1094,15 @@ "url": "https://opencollective.com/vitest" } }, + "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, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -1064,6 +1120,15 @@ "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, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1323,6 +1388,12 @@ "node": ">=4" } }, + "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 + }, "node_modules/chardet": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", @@ -1400,6 +1471,12 @@ "dev": true, "license": "MIT" }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -2183,6 +2260,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", @@ -2279,6 +2369,18 @@ "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, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2547,6 +2649,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -2773,9 +2884,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2798,9 +2906,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2823,9 +2928,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2848,9 +2950,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3261,6 +3360,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "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/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, + "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/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, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/openapi-typescript/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, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -3469,6 +3629,15 @@ "node": ">=20" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { "version": "8.5.30", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", @@ -4417,12 +4586,10 @@ } }, "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "license": "Apache-2.0", - "optional": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -4462,6 +4629,12 @@ "node": ">= 10.0.0" } }, + "node_modules/uri-js-replace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.2.tgz", + "integrity": "sha512-VeBBt/nMxedczht3KDL2yBBMJqIxpCQLMZp3UJ1otcWwUHdQW4IiyHpJivExzSK+FdxTowRfGL4FmwDqH9+S8Q==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4819,6 +4992,21 @@ "optional": true } } + }, + "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 + }, + "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, + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 1d48e1c8..753985ad 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "commitizen": "^4.3.2", "cz-conventional-changelog": "^3.3.0", "happy-dom": "^20.14.6", + "openapi-typescript": "7.13.0", "playwright": "^1.64.1", "purgecss": "^8.0.0", "stylelint": "^17.16.0", diff --git a/scripts/check-api-compatibility.sh b/scripts/check-api-compatibility.sh index b749d780..93f662a4 100755 --- a/scripts/check-api-compatibility.sh +++ b/scripts/check-api-compatibility.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash set -euo pipefail -exec python3 "$(dirname "$0")/contracts/compatibility.py" "$@" +exec cargo run --quiet --locked -p marreq-contracts -- verify "$@" diff --git a/scripts/contracts/__pycache__/check.cpython-314.pyc b/scripts/contracts/__pycache__/check.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2df299a0033b5b03166e6a2eedb2b039b0377e77 GIT binary patch literal 8966 zcmbtaeQX=Ym7iTMze$mlB~g-PX>HkZXv_NWw=Fx4EZLSU%VD=N?WRgtid;!+QRI4e zX-TwmQG)|2Cn@X%zKhc45C>PFz9L0cpaoQ*z{PNYxQ8NLh1AEb6{Lr|`=ftd68rAp z{BgK9`$dYDo%U`Z&hE^-c{B6o?VI0wqn%}57lKs#r$1i2xB;Pm#RVf#`pbil;AI*y zNJ1|mhUg_ELVc1FsXi@|MSW5d1y8cq+GCS!4Vc!_YwvMLjvlAv>~Tr19=GJ~@kpK? zujEzRsot_4pX5{Dt-b!9fD~vzj4gm8jgD6Qz8yE+aD_*Q*oChx;l&D3h4!uZ) zn~iA}o}1B&6`Rpsy<#t+y#Pm|QT>J7*|U*Yl1s!SmVTaRQ=Q@7W?D#RpcqeIW`)=Y z+YE2xd`#koxFjb{(c`QX1NvAq9ZMx>fxXPJ6Ex%rHWg=E&V~rZaam^>vMChv{PP2f zw=12JgjifUAqZ(fp_1uXLb37bBp07j>@beVrBjMsV8wLuG7Bu6BWwyrm)Hapyka^d z#M!7iFEL(N9zqT%Sm5_yp2RycsIL&2YvvK-@S~Ia5aNjl3S-)m#1KI=XzVPC5Y6ra zLD*h+H{KZ1kfYxm#i(gOeFqS7qWU6^jO8!kc=839DH$Qdcx5GtaUO!25m-YV>Z?MC zp@2@+*0qF(Kngll2qB>Hu5HI9Lnn9(V~to;zQCytbWJa5qx3?56!tydKVnJ&);y@K z`3dp`tz}&6$!aiQ@Qoltf_`+4%#f`pL!RUiy0j$>-(YDn=DuBT(ANsz!C<993#$$q z?}cY!pN;X1=U}x#6$U<~X)~7MrqbGi6b&xnh9}buk$q#DoH8U&Ho^)U;K!333c0`b zjmHFmy|gQy7T8u%h{sJu28LKU_rUMLDr8$w11S0cIynU53521hG%?m9H35MzM1%;# z=TVx9)Z`i1W+H5U1O>{ce*ne?Bs68|dlDDMa&W>_$(JzIB$~E@;v_VIFjOPjXW@vD z?Q2VmBr48mRANO*j7GDL7W@X4*-FzSfRr?A0@Y_jq)-O)&01OnF+?dgF*7{OO)6GC zCIBZY6&q(22b)TW6Pz>}A{DZwO`(RlBr9T(G||ez*3tAh+X^-d>RKn#!kEa%;%uw7 zBduaw;CM-FHO7RuQ8qr-!cQsABX~-ayzs2DMK@8V9Xygu$74zH*%p%qE4%n3$o>TT z_XR?g7c2`+U9*XguiUvJM-mIC5;q5APJ)(~iB9sr@#7TvrTMrBrJDoL8YTyDYnc2F zZp9C^G^CY=NFa%m<631(>mrg9xGa)aF%fXu_`3`RpwYi+Ks8Wp-~AYC;GI9UYD4mKRxkN`|zDUdbM0vJOi1aXEx;~Myh zn<3LB9k^(Nh3?>Nw<>N{%u;V}d~4$awfsoLD*tM29TBn$9(c_L^zTq)1vr)P60?5e z(@dr_>Y}s$ViDlAXUoRp{H{VDLnkRv(8-V|TV=4&zi#NmgSuunmDUesxMU*ArH0eE zxwfX-k`S^CfvG~VB_Ni1@1MEH;%`+ z6lPrg*uX~1MpI;Kz(mVMF`fE)yU`7c#~_S8P6+ZUi23p6kYBNgtfbg7JjOGsvM5$z zB$*x({IIz~05ZiXu(1S~mq|&n#1eo)#Iyh~L$PyW6q^O$Aqk3GV3PoOF0)Z7t=LCn z;%Jf^0@~5o?tS|em&#*k3QUvY9AzgH+z1#cus&ARuqe(1C$MoTEldgcKvL}3c!2O# zb)zAv3aj`^jD~;_95!ZRGnoHrwCeG_eq#E>{mO|M=ZeF7-E++YdJJ~sC(>&0Mg2X+c$!#6t%vubM4e3^XnKcP;pL-HtE$ch68OZr}Be zYaO$#OYYF^u@5UguDw&c)Y7+7Rr_}8%~YX?fKT5P9o+_Zcy}C>Q<5_i#2cO5KbBwK+cf`uri_3e+h!wCC zSa*!8c^hwy5R9Xk;%OKW#q?4vLlaXh`>N!UY`@<->VU=FS! zOCLxT&!KOtJ{LFd3gom--i2G@+%|!?v^as|-dm_|&=#_CsJRY>W z7i4dHtlD@&n-|gHN}>k_ecDH)>=USz+#$u zAMqO_4mKK-GHi#c5dd|csEf5m-v(?2HW>X2PlJxU){=SUb~fdX_j9v zQ)<}`p8S^V#zZ&FyrBN|kmr22-G8q1|36qbC z=nvD`ZAEQr9X-y8B9|JWxs*YCn1(1_OyFWEfY$(m1pv%8&3#a;aqMPf1CzUS7_Sq8 zdsFZ#XuT5i9*=>W2m|}f9wV8UkPsD{_RKcKGSVnamWu-fPp4?Ly11v`>^TZC$RsOZ zh_1e5%k`IH@>(-;KtoV~8-_R}oe;QT2`16lK?|7_{kWHJ^Z`fz>z0x|h5a;R5W6%G zze@{*st!%|9)wY8t)7m z#t6u^skm3m7Ykli4hj>JPlQ;v_!{fF*77e zoP=Y%!)XDQKFW^AXn5m5KSm(vie;{j*^=F0N;;DQ_2h;**m;7^)|&Okia;Rae3F&e zfh@xd=|m>Z3Wepu;1DQ*a4#n%r(ht1xPu<%Qk*!77e|NN=$I;wAr>aa(tKtJdZ_bR z1LwiOYe5CZ>TqVq8HF1 zP;BfZ2k|ojha&|*t4MV%6>A0v1>A}QIH7XI4>89K=$`6q=7VSK_T8l9;NC_5-kf{iboVOdp6Sk0!BxsL zGw|1E?^m|YI9DBIZ%8-0XAdn_Y?ZfnELL>nd`FfXN3na{_HPx{?^eik?_y1FuHy7` z|Ek?Jb2MkKnH|6VqP(diZ}0f+#D_1-=Y|$f#dF6J`H_^Iz6vhqUlD!ee_};DLxOiZ zO7xQtA(94<)v}Fp-RWFepX}&c@%pAuuI_p6SI2VRu3sLz9lKHW!>QTLvps*eQ!Z<| zO?+Aw`sK0dld`vKrMYb``1Ahd=3@)Z$MVgcOM$_K=Faznw*vR1KV*NG{p9K$_QTkX z_8U#JTi)97yQ^*a>sIrpBq$-Mj6il_W~|FwR(?$CVWe5G7_Ebn=44P{%--98uj^>;t4Ufg#&?>=pI z=$P-C-zV30<~_&PbZF1Hch3#~dhEl{;{G#B?lWeGmfPZOR<7N<OB?^tf{Txjpiw|6bN*@gD5_Yc3n|5oSi-5+1Rb6GwcUp|${ zd+KK{U7xr%@%q%v*lb|7eYP>@sRz>O?oZw9=l0-i_j^6>^vK&fb9Kk>ZOzr4HB_ML zV%{+Z1n+h0H4E4_RU{kmUB-Xn%Gk{^@xQfo<=+F{GBg|+D z#M{j{htUqVPJZJ(j7?cdf>oQb0b>p?k&NR2w(yV`CqBlERKT)>5DCx{%-6h~GU{MG zy8KiG-&pX#zG*g$D?%D;-GLbQpn0!V6f5#X$W}}7ERE4%7tG$;wa-g%xV{8Y`j8i{ zYTXG|gHnJYvQ_Kj6U8M$XcQu=LWa#AK2d-LMTo^fPbwY&h^E~#!~Jk3DPd@u;D(3c zEVTiY!#Bs&D4XQ7<#;^xJTt*aww+;(x*6l3+K$f>}?KMXk+qgL2OmXvtGo!MrTH z>lWN~Id?snM7w+0PQ#yUr&pa`+5g<4^SNcmnFYt0-w*uZg@1Y>e_-!I7% zR~GxPTd77N%=cb zx$=85cQNl8%TvkE{Z%vVGPP;86EZldK|YRX6t6ZK4G||pK>?dFA%-*T#|opE#3BNk zs?t5SC`Lk2{a+cbDAorVz{FH!yAC- zhc7EZO(;Nbl7Mf_gf!0Z?Uv#=0yjUIB>Su|2}S(vi{FCm3kyLI|AGeYBj0^gb|1O# zBiFy9ou8xrWz_!}s{af%er59z-Q>giMxy$wGlY|<|DqZJPRiyjH7k_;2R*O$TpM^j OvP4zDkx$jCL;f$UI>}-H literal 0 HcmV?d00001 diff --git a/scripts/contracts/__pycache__/check_baseline.cpython-314.pyc b/scripts/contracts/__pycache__/check_baseline.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74006e60254830bd3c99e11b53c67a4805024417 GIT binary patch literal 2385 zcmahKTWl29^~~%%cGuW7#*()V#S?F1x-RyXVhd<33IU8^!EDBOA;M-g-nn=OW@pCt z&Ki5AN>=(&nx7<6KTJeb=MVn)3{s_%^tT^tb_rxiq*PTD_0OsM@?X#0-LXx6a40`C4;%YdUj6C^aI4;0F)!1>`8b&(>lxi>_t3rO4XwSsF>s3 zdHx;?(X*C?dqb~skNr-ZK!f-dKjw3x&g_q9B=n)_`pTjvP(*n-raO7GH8$)J=h426}3o04Y} zp9Jwnf%(>)O(uiboa_1wYtg$BD1=g%vOvL-X0B7oXDL~<+%nTHd&D`FJ+FC`n1+u< zzDTHb)4~!D)nkD#632Jx@-H>V)ruCwy;U%5tCW6ri%k(k4Qj2A^# z*|%+M8I?2@7h02{5k&>z5&UmXA{Z0yr!jZ6C`L7q3$|M}Q%WEeTzofb65=&>GR~?J z3nKHxM`oAe$zLqPpTTtP|@&F4#o<>d2~*G#v- z(k59XHva^r5jcit4KEud8^1S-<(Sy3jkKlq25LT!{P{fX;rWtoVe1Df&$MmLQ5@)i z)v(Nh_E#F2@-A!i4dw07$S=3RF-jz#4?Hc#QOuQiRWGY^{)Qkz4bu&Od9-F1*w-FMl=2oQ4>3Q literal 0 HcmV?d00001 diff --git a/scripts/contracts/__pycache__/compatibility.cpython-314.pyc b/scripts/contracts/__pycache__/compatibility.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57d09d00c3473d3f7b0b34edc75ca5dca0cda550 GIT binary patch literal 23840 zcmb_^YjhjOm1Z~I1V93Ofo~EN9}+1(^nOqhB~cVbiF(i=1?(ioe_2HIYuU)V|wFRGbf%6eKxzHXEP_7o!Q+c8x%OOW;b&(^CM^Xhb?(` z#@RpnRW-WNpdiWFY)foZb-k-@Ro(jTy|pzj*T~>0|IM$iynK*heoZ{mqZJ+OqYEs< z%rFin!dzw?>`As)6;ag?x9X%iqNcx^h=%@ZBU<{ai|FvHKB@0DL=368IguP1W{eo= zuPI`}ujZt=H#d@3!(1}dGM6m7P!@UmYsFvXdsoWSze)L3bYE7rl&8N&3brwh!YMXV zSj^Ni4s8kJ&|NCpr50mEiXHkACSr3K@Gf!W;9cq{au`b(KO3<+Oz@XE%y^eOa`CQk z+TFpakSpRF^Z9*|n|Al4*ZpGHJ|5)kzNx9{h-=I* zMhOPIfrx!h@*Uoueo&#`2 zeB&;6#O?`R5BP&Fk1sIM?wXEFqFkRFRSyR2V_ttSFcG##g7#x4+mCg(8JpOo$rT6$ zBQ!$zEc}{&S7Z{uhHEar&*O@C-BRa~-xNKtk8JSFFqfDJ3rbX7E{&)iY(zu)u}PiG zJr)c^IMn7i#|63fnX^sWq+xV4;F|J|jwX$xqfLib}PX?#F?Wildqy2i2dohge^R`d9 z(4SY^!*0$OiiF$UsrnF?Vj$Z>H#r@$p|4Un!eo|xi}_Sz;Buw!?o^xMXBbj4=$3u- zZ{V0=M&vLjBT-X-impj?>5(Yz06WS~tESbXG7W~<5seh9pVVc;Q_DD6Dd%bRw90{* z?_i|4Q^V9Tc4jNHIhATcMQGoL^`MHWq5m9+aX?+g_}KT^vrQU~R4%CrhP;8K=B8`P zpUern#=WD2VwzM*4TyX)sS8IyR8wJO#x&ZwJeq{W;K-;~;xD`nuNme+PVTJd)$-SB zZr3Do%HuiZ^WzUJg|FTE&aL@z!P4;0YQ|WB0Q?tn%6}U+A<>`Oj;ghvs7*(UaxyyB zfH2yz_i4_`jvXQOpwF(9OKPsCnpEdm9q3>%I2%$`8Wn z)R#UZgcw8@=?wZfnf`pqo&I&m>7^2-{K&+JZrQT!<>BqE`!9`nnwv)2p6rWm^4ri+v&(u_xKKrkteF7xo1ZC*bxb)fl*>it{FO#;5)M zDObcjNl81YjZBC92%mC=l4@TdlGKJnSjjNWwU|27-Y}u#MscL|sdb0jgm1O@3qOa~ z4D*GKG27<6Zx19)wX3Gu4+=i4{MVKI9vAPPOf>mcn|u%QD){On%f+AM@Rg^8yguI0 z_rPR*v+&J<`G$A4-rX8=3+2s1{#L=%!fRUS1j`@+G7l&TG@>?{bL%N2P(cSWwL3Co zs7YJ!V4kqsR4sHz6~D-2W2R}yT&7QY&?d2(Ihp;)!Lu=rTFMuMA(M?0r6_kgDNcwb z1hpTzdkQlN4Jp`cDbBDCO-SWbc?(GVp@MyHW~qZ#%72m_D%eTZT3xaT>)f=D!@4+z zeZw2@j86Fi*}l4NL>qhox~gy3&ZfK(7gl-~HWjbm6OQi6SS0Oouv8MZoX3ki8*SK- z%op%YO;6dqH{4#YCyXuZ2Jts(xia)iQseQtBcNDsL|lRiwKb_ZLU2j7KX^TXiqIdx=_&XbKm5eNnHGezl#@^AyYBvaxh`qv})S)u6v>3hgbMrr})!j{7!eG z(H(De6Vj|aCggSVhVFHJRqT9R-%P(bguDUXFz~>bKi9yQwyhPk3&w3T$JTfE-J6c@ z9{ld!x!rtO`&!|)xM@3o@$w8(n^yHD^T*ykm64|@Z+1VgDH4gA)LD86`N6M!^p9A9 zzA{l)5h8FhR^~jG{5ED<)nj3pka|QW^P%ldm9z<*RrO&d%3-PzFQl%^*px=(Ek#<7 zq)9M=T9Goq_(-Js58{_h0u5=Lntqw!rDkDz$t#_dqX!cTq}xTw9`(h_EU%yrwNsP1 zx(>1-tyAOBC?`X|yv{1hk%`{Q^lU{h=!TsA$bIAE&^feH8?%P%9R?{}S!PJv*DChj z2=)Z=>Bl78_+TTJYZB59YWs^f#vqGNf63!trOh9ssXMQqR=1P!L|NBC5!{4R=V13; zXBcrj`mnzYO^m7fPy;%m>X1I9lcs$Qqo9E2+8C$m9P8AaWq0c&TB!S7>VPt&IwwpFczmk*vnl5+jq_Dzu6Ym)FX0KRo5PEoeqSjL!lr?_C+}> zu*d045Y@|hu^D#QJAQ)>X3m~Xvh7jTt?l^RmSk@=nN#bJ-xrP~^;kHAe(ZRSh?+EF zALSz9>%NG%Z*q3>6>-40V|&%OgQ1&AgU9O)QI~EyL>T0x))(MU?7ir{Nte1X;d>ezvBKe@`!7dWEBe|EM|dChq8a8N z_4#x4Z)+0zs#Sf}-*t2fmct*{FV?@QpVusC?ps?wu3xbn<_(9*O6YmJTil`7ErqkD zb$#w!(Km0c=M~KD`9uG#dfig++O6BS<~<2Z-KwQ77Jm1o2L(0!rgMD%z`beSeqJbW z@@D6He%Y*Uz0fwteShzrL$4o-72Ypwm_4>$T=7od-M$6Qf?KHEk*Mrgsq9$VwOlAv z9$zc&`A*O0YNn|1AM=avRDG{%{+f{Ah)fK5ujSs(UF`npsUM&E=ycrX`0?oioDaJ#W(l#umhzb9M6oypdqUZ!`=}_-bwXqwPXF{R$k(MTTF-6_6fMWE; z$H_jK2TGMk%Fu{q;JjTn#fH@QEyu4WJ-yTXk>ZpGZq>>OH$F2&QLJ%n{|=#(328w(ybU!voX z(W!T6DINnl09#O^jC;nA>2G6$`A(x3M?yOyyJP zt&q^$4*4_yYI`?DZI$RxpmvY<=TL~8C?;@tMnCA4{gBxU`>-LW>VWO&DRn4%tPY*2 zI#m7Mb$IjltwWmflq*cO+3qm3$p>?3EEH+7Q(=}g%9TmV2mJsX5bMyB=T0$QIcym# z2fz(7N3*Y94t={^-;gQ$3YHPa{Dgdr4pyEgxlhPP87D7mpPHn$D*DVUeN(RF(l_+U zS=uM2Y@;#sid-j))0{QNEjfnOX~9m%rl=JRH2OIUQ$8X?BuzF^O{a30R z=25@c_k&q;a^@;`+B13SoQM7!&9?;5&0~2wa>NyD0{eO;10PEdCr60ynKjN%e>v%~ zd&0;mV~`!HG+WJ_I-K6CqmTNzAXs050^hWUgVUTFxGzCuC`{~jdqafP9lL248Akxp zoP8VswOs_L0Wmvp7WalD;SMmteynmuo|caEP$j zNb*=HN%B}eZN~EozS}+N3QVL*0mut92`oDWk?k(G+vfq}3pDno9hCxbDgNMBZw5p~PSXH%*hkmk zm?6uA%%I^z>TxjXWexCdiYOVy*$vFx6VK%-hEZlw2vgan@VyU+K5{eUZPFywI7)D2 z^P(pjt{pC5xH_@J#N!3wFC_?9OOl0?#o_5Ope}6(sURWT*+xH)AYynYUNg+P)pp1D zx^aF_Y=>ZNoYg+C6uf;ZHvNGoQMq%qa_8J`!E%&099?QyDiK|eLwTgl$c%k-1&)L4 zCNN7g%plXVg<-Dd52(m?cz~6*T@R3V%1!}%+t-^Bf?gsa4i;&Ula!*Fxxvh6v1EWX z6j`b|X1ChMLiC^qJ$fg+Tv7)*8S`>ysjvV6sy3YdB1v{E1u4ylXlKsB32y)-z;!0e zHg=30^a9Eb@51X}euH-Khxzl)*wMQkiHd%{qJQD);>q`KCECyN?dO)a+;e@> z8gK58SM&=PhrbLH?(rLzo$MRtoouXdCmYpw*h!z;qeWzvL;8}M1>#IUn?Z4MPl6Lv z_9U1psV7r?0j7-h1&$%4FF1l`q#Dm**=M*8ypjfS!h!Hf&W(1l8#n^-at^%4@fXIa zC)Ly~=l{BSxqk7&{iZ{I)BI&vgMe@37qL-G2ZUA<-UoC#YDSxY*G15oIMWkZO0CzoOuIN2M#}4ns@j^Ps^LqW1Stbh?TY}Y)_c6;u4zBc=iVExDFR&^ zQVu%NT^=F{OXghXaI~gQBcyBq0dN}kBuyT#DEGna|E<1WoDRP=-)QBg!;w~qp1l4} zvR843$*YIFj*u5!H}ol(GN}iQO7P000bfp%13p~70CjO)h@Z?sz-ez_0uqxPgosWK zCk7^&i=gzf1FtWT_HxG%rAZ|ge}uR-+za^OJmfV-USt&Tb(FZ>a)$XVk1^!M zfy(ZQ4ZhX6;9TTB7+G>IPyf5obxZzi4uL?5zwx#C##r}TZR>>#^0X#~zFMP{mmKR5K^=n}1F{a}w*~+@4oodV3&d zduw>jvSs1qTHfv@{aW7PnG@@|mYJRhmg2dIKl&O#7gPRg9k)B?+^-&Y=+0 zG2L6OGu<;sXRiKtz2Q#no$wpsI9-f{@{v2?JK^u8y@`^M)sm4E_%dHQZ;WmJpm8z( zM@%*wLgAosqx=$U8?{>7b(4Se~&`{wgC$6~wrEoXS!+53ibfW55G zv%e}VyVLP{$Gm@`ODJrgIlZ3U&sXnS^azCq(7sF| zcInvCZociPP~LUleC+ca#(L;8BcnBa`^?wR%$paqLTT&kn?BmJ)WSdS;xCNxV;zI6UX>{`6CHP!q#$LF2zT)ca6LBCieRP9;P{oMGmae42(ZeibZ zyz?@Dev}_|@dIOm%^f#*K>0IgME*d987n;m8p7OVABpg0I|I?z22olz&Wdcok9zU9<c?9gyHa7XLzM4GN+p#pSuNC_K}_`X~A4AX~7gQ7f6H&LX;oEt5L%2 zf=el`8%m%2)oE0AD2;b%(u{r@ZG^x^&ONQiks-#}A(2%`n?A=6b;)0KKw9-qZ8p-8 zNZZOB*Y>=hnh>|01}fC~ku&QlUrK{>DdRK}Br9Yd`WBUVQ@tstJ}c5{Mtqi@vsD>* z32>*O$&5*qJMf2Yb>0I??yFyt^5DnyE`?Qs16E29Wy6g_=@_Xy(b4i$uo zodwQ9I$v?X2Bj5er4U(jQ<`C-R^(IY(&{WgFXw0UGFbjT9Fo3r8Q51)qe5qavPZvK z{U*@kPn|bLTE7Bi{m`1HV~$^?CWUEC_X+J(#vtop5vfU51anQKnh|n@p^vjrnhlC? zMbbAVZY__~v(}GdUVv#rmXT{S(ztYZ8l`+1*M`@YN z`*F4h5{FQwv)q}{#!!{BV#AkeXI0j|tWHyvv-(*vXj`APJ~7Tpj7g1>Ql3z@f(D;Z zwjxHz9;$KHJZ&VL_NRsZ4t2L@)cyA;Gjp_h04s({pE+afzvB$3`5ohv8Bmt^fxTZbJmTqp== z+CJqALtl3S=&3zwu%}cuqlNMTZaRQdn-7YZp6Ce(D^hyv_DSePrGuc!Kk2&W3vvfG z2rW`_m@!C{BV+KOiDmRIgj6}I!Mz}IdV9#GD#IwQZ+=$pCYFOJq~!Ur_tf45k?QG z^J%m^?M6G@NF+z(2~4pCg%}GiYds|fCUo-8hz0f^xTYAi-0t|_A&T3 z=AP7(?uPg(YE4I+@_KwO(wJM1e2jp8b}~@X=zV$b0mjc z5-ts7IGIZ#rM)nVAS0VJK+=H8C%Q!GYSKhfrpPC0BI#0|a4j;H6#Qg3d!r+zc~1o@ z7_dJ_n%npbzXd=G`pu;^F@sRLlh+hKD5;*mE|hG8uJHp+{@j+hrh@!#j%&&vSjzbF zt%7CuOz(rdQogK7$lI9$qSuQm@0@-8Y@(?CwgF&bN%?Iv!4PeC%&(i@$m1iFEvvns&AaZ{B9x;o~&dHbHl>x;gn8+>_h+N`|EQhx>FC1O9wIX2F$QM7izPICd zO;NfjQMzTNbj$tH=G(oqM`x$kE!KHs+_GuCu=LLU*Z0p~OcZWeE!?tRRDS2o>t|w) zSoeZuiCqjYHS?Q}-!GyHnoX!|QJFIOV)@e1zo<%V?^@a3wcL@|es*>HS!EWgh3yM< zeCr8*Q*XTJWU4FP9tUd9{@JBO!=9CfJwWIRgof^A?=O9y`1t+{{KXNW=MsUFU0&WB z;;(XiD9n#WgzD-0)@z?zn6j;3SQ)crHu~yeNKne(8cmp>;n|WgFo*c^&bayTdX@d1 zp}Rw|ON(7X)!wDTpO<`GvRuXYJB0lMb6aK&5)l|hA0WK=&d9Kn zFFzbNA6c(%e8+#+zp#ICOsL+Us6M;`Sw!{G+h=Ew&+T2e6wUX(bNcS-g))>^xi@av zs~CK~e%Ipp#S=^W`0DQa*5khtr5fmqq4$P=FuYj0v_ojZjtjAlub!B&^BZCSBx5kP5|60L_biQJ?Z{1QZ zN-P9RZEQHvuzRIpx6rVmGo?(A^VL0Z>xuQU`b1gN3RbSNtqU(E+B#R-I)%0)iLxVn z*^xPIn(l@di`N?VCK?W`G#ps!MQs~S-aEh#jjZ)uO7yu_`hWmm;r#)jFUUu(@!{*A zG0Y9NONF0fs*~iWOLdC;aBQFI5yRA+R{d{=$v>m|Ld}#PWxvR0N+5O{VwvKGFRrja zir=i88-D|KOVzxl`pbvMS;ppIzf2ZCM@9R;Y^c0YqPtg8d7)C5A-c!fEn>+x-~g74 zxA^ocMIotgwMz96*@o$9Ctiv3%%-U&o#E;jss;c_fgT*>q~A9N!N89%A)t{M zkB*Mrgjr=WXVQDaX-A zynUs-T`1o!n06#g2Uko71yd)l>3rNMMvufexw4POG$a>jDHjuMV%vWs9hv6gB-0mKTmCp|zPS~~?MjC!Dbsjv+qF{vKZBuB=Wq}XXums}R~ zNul+ohiIxO9ZPaiO}K2eF{DrJursS~fntgDz+^}JG!vKpGAU;I*Qt713aL$UowL8{H|pBkZQvi6i}_(!Zg+}$&&N-hPoznF`Cp!X;aXrihU^qJbC+dER108l%9`xF$9$hi>ePk}Jh3hgk< z7cT?wz;e?a334~1TjeC-n^Q1=e-TG`w~L(3vqVOfB_LrEW=ug2CjS2P9RSgq1F4$@ zh~^Jes$e&72Lu4aC?F6}*;KS?&#qUZWpdeQNh@{-k~=jYPU>*U0bJ1nQNY|rjy1%x zzwO%gWR46LkY2gY;c@}F*)$ckwx?)9G)FEXT4VS4!mbI;8wTXPC?t%>(QKCKR1PP~+$ZEksw8)5ip2-&;b&cU{icTQ{bQpYz z=i)pBK=+l}osC9<(25GIm*Hk>7vDU-+Lu$TLjR(t$%A6qT;LTc0p(UNvq0 zVCO>gM~9at<6C=RhdO_A{zbm&#NX=f-4J>&@F$+g&I+`$bt(v;z^u2uLNg?kPZ#V@b{<%~0*Vc*}10rWiXw`H`PCCd}o)_|*yuq1DLO2RJ^u#^Q@-_a9pRb$}@&ddepln3% zTGOF~sdLrT`HRMTMgOsdcZ~3tylal}c-Od`Lzis%`ydxqbgy278iGFe+tIH_=e7v? z3chA%T)%Vi+E1fDjxKK%_MYV(!|}bta`JSqd}`T3RFu-HTvz^tC~?7jF1A0mb>aEN z@S>MLaFMqU3k4&*d4y;vH9^ftYo6_y>sT{a$8@pintexHvy+_J8|TZ{3hLvU28E7H zYT3fbGYr|7D3kOv%mSB6sf>_jd6fFa&?+YLg+9_EDQ$--GjzQao>oQx^Hnk>ZU`t5 ztO?Q0p^rm0sluiU-CX6O4AUO6-Ow!rVHqavj#4UG*_fMb#R?<)GKghWDY0x5G+8&L zEro|NgojR5`tF5vtFtj*k|+ei+pQvS8&X57NS(>bQPxRju%wp4npxJc-q;$tX>V+c z4F68DP|wgRfMJcppzJ4U%f$w+$$DQzPFfHV(kr$~xdeyNVcMq3S_|3O18j`U4#Mop zn5_mGF-+NFaPvdA71s`==^$5X1?a$_$VZ*Y>Sk~GwCaFrUk4N;41~IzIm9iRRi0t7 zJ!iF}PO~-8{VoR+@;bu=F_r@vYb~Bdsh~NOD&+PF+Ur6k(ye8u|Fs~#EPaD5U zt%4>zHYKY4UF6hCbH16V>}<%xQS4Or%dBde{w4OA%**q+DI0YhacF2KSfCtALjlTuA~9?91*156Fs)^cBB*=MK4zZ z-_ZhV7^>0MBFRcx$R$ZMF*P5ba)+W-Dc&TVSF3_ei8e~NUZlfC=1XL4aCs0dA=;9{ zsndLvB0(yW%ovGmq)n;|;9kYOLP--8zDFU*A@?`IHGAPsI#weNw#X<`{x=Sjc!ycTDP_8qT$L6qIR`VtjCJN)*X%5NUjCK%v z8L42%Wstp;mB;J$L$m-@e^PJ;>_PURo#b#24K1a`0@lzF>^LxAV9`R3-YL5UP#L9M zHf6)1;=YT7lHCS1T}W!F4mySTAGcdbZlWTyo1;^%fNvc5McTAQw%|}FJ@6th zmff#LMP_#LT}{H&uxe`fpm5>BkE&KXrs7Rg;Jf%rmykEc8^*wQ&KcJ9Wqj4axc;C7 zstyO@M*=ijw~%+7Hyr;vXGF-GUU5e5dEOk3mHt`vV$nyDrS6|Z6MK7C_Vxe z5j=B(&zpwqV$QdgXNP1&pZD#TzW&nOegPN4lrA)_)$WSxchQVfw-<>@{I;t3iI{J# zdYfR|K67rpxbn{M>%)BQejvTYoik@2=#8_Tg1&tIm4zw3npBi#F(Bj(@`k|&xkYoA zV`tWicL=#VKN?!PeDBQKp^L)qVF+6EIp4fa!w`XYfus^Un^@_cz&&H zuaLj*zNvGm3$K*&HE%l|H=Oy@S_kn>Vfp;7?;n~qJSZxk-}lbJy9XC6Ld8CzX#edT z!lstTP4?K}YQyfO_IO9X1Q!p^_r#hPh8CY+Ydy@j9N~{#UVQ7LiKWTq z=vt?P9~|aK#@7acTVLh5m)3kQ$M?J<6yLgUxCN#C{QA$#Om5ly_}k4fA7JmMwe8)@ zSAeM=QJqlzGx<5A`ds|^g8ZCQ;b(yD7eAg;tps1a93Y+@|eaL>aQnjghrKiS%TvQTqxU(v}@s~hwtKZN0> zOmq~>(pSJ>5leg=%-$3;9wKIbGUF_T6PD4T8Ua(2im%>_a>xUwCVPCSf;&piu@3EC zOope%lv(8`<|VU-PmEzu)Ga5YLr69dk-ks^>iDQY&6?X0ErEc)Oyd!-?@Ke91#7J z#t*ZWBK6M&jl-gBN!B{cm?845m8$!Ue%Fq)OEE{bNv?jv4QzPGl}eYvJWA9l)r=eq z9Qn$+$vuNPR*Ab#>zK=67wpUk*$wG4QZKuRy##$m$hJkFof;kEp*O{abdG#SfunE) zheYuilxJHT+pY>}tDxZ-(jC^F_K3eecBDm$i(IHqqm+xIcu)sc2KTyTr2M*+(wDk> zv8fa?l@a6;HMYW3L)@DO!6tO!8#O_U7Pq#_Hg#ZMQ(8Erf6mjd7+ix2RXa8q-@% zJwc#(#FSOOfQjOk6hwvb`UJHYmuv>fbx`BeR1XSwk&4xD0TrYQA%G~Q%S@v!>B@?2 zP}Ek+%uY?tq1{9=!syX-66p{i8J!X!HPuD1_!V!FN@Yu~a*Y5=B~|7A7X8#i*}SvS z0_pG|W!7QGnCN@~V&~5NSHx^8hfysz0Uj;vP39ftCg|Ny{lp99n9)((21PD9g+2Nx zqS3&V@Qo%RAH&H)xz_X~K`~S4(nxKTxwNA6C$S|2!RCCZ224Kvei8b#!ON|XZ`O45 z6b<}@gb}tZX>^i%2Ivi~N&UG2(P8NG-V|LqgD_F1PL3?H$(-Z3$1g<9MYSQ0+##vC z62|F9>zSSkg<&~FxKz?)4Q)`4Tr#Z*_a8{P-ypC5gcr3OO0kb3>!=~HG>09Y(P1x0 z;hqn2ixau^tGV?bmBe%R1Gm!{6Pl70O$jX5B0sC=H6?M)VfiCp+P=Dfh#-BAlO{I)%BpHV!r#GGk4DjCC!ky zm(?aVb*yaaSSvdKBT7rzeAAkxenFGi(z&vwGj8czZoW6N)^(A;JeIh8edY4?c-M90 zhW=QSOD%@+twYo zbkn*`2)0SyI{6^q9xHmU>IYT0L2Fj`1?&Nh->rYG`8&;XuxXDQ8{?YI5Vp^~oUqo% zt@X3pdCk^^TD<<{3lo!D^0xln(hv6YrrlpKtoHDFL3K}=Ke#cpU`#CD1*2hJ(8+VK!GlTsCr_t}EoD3U#9?^*V?J2sg>SL1Lh~M3jIM$c{*I zBPY6WQsX#xZjb{%#Bl#aUi9Qg=P%jW=tY;J0^YZXm!9+J4E{^vCDWKr8IG`_xEkYz zFG5>JCGO(9G8`1|TJGe?-Oc#VgctEb@3ZXZ8V#%YUq*(l`!!?vlreqEpkRHz_9Q=_Aa5Suqb-6iXB0O>U%Ec?Pa5Z-#5x85?iudLE%j9YPK0j44=aY*XmH z=uDXxb>=2Isq77`Mz!cazcqE37bTpCB*P*%G$o0HVH}kd?ot$soC0H+3&kT`I58nb zV`8M8PlPXs3WpO(MdZXuNen9?C7OuK9Vh*?#_*?34QWg)5sHL!d+9q8jhNu~u-uGL z2qYf_*^i=emT*3Z0!WtwC>nIG;3<6+MY3ZPaLNp#eeimO%hL1AE|A%1!Q!2Alf2oG zmETcJ3v2DHK>d9PNt8fkR~^^)IR>^8sGLT#J6lgsJS)+BnLr0&rnK=kH}Eb{#p_pm zM%TNFUgPb>qa@vgT2L9UB+&bY1nSe3Z`MuUbc18*?vcE9BI(4x+yOLdXnNR+$hEh zrpQznDOhLkhRbjeWtB2BY?L6O_lx@r|(=n?8>q7D4A*k$l%(L&Vl&a z-PyI=WUZ{H@>_HI{V!R$14-bQn`&umVZ~sjv3sF6&Leo@R}i`c8pDEMe#Bayjy!h- zIr%#9&3efyFu|(Ay2c&f@I^i(r*x_n}GeggL@8F0gA0Y)-#1p%LCzCO1LMJ=|n_q3q=d9)}yu&y1Et{PUfiY-` z&nfRK9zF78Y&YJ`bCdN^h_u6QLw>$>5IN9p+Mqp0`5zn7tjYL=c;ZsLUo*$Wiy}@j zJ}i!DR6sL_VzE5XRz*0a=F&IS@|76&wmHa$sDHi#Vi+9nzFm)=Nf{5jr@7R<#ZV@!H4MDG&SG;UN4CNYix+j%(=*UXXRge2o~n9HdLlhchs zp9qt4xRpGCfR`1=)Ay6hi^>h(+ih>PEex&KwW)P&KfRn2s%1N+F$7>Yh(ZFsZqI(W z>80|*fQpJO8bE{evlI{P2dHODMF1vr>w@DE>-uR-!15#}A=+=kBpPaFuxC=#n0O*0 z$^_e5x#<#FKz~SXh0DKn+js_k)49C;Mzh-3xxkRi*rK@7*tuS}N3GlQyUwgsuW$2$ zRPkgirdhyBqap?m#yB*UvOGh&Ud^ogmPQ@WEQ!(ckUeNN$UY zY1Gpxn&VP*H#mKdRvn5&q9oA>#e#YIR3*o~a6z2v=41s&NKQ@O*{lcHa; zX_PHR+g?spO}1wpxIGz%0~Z~Oijh=PGA<`2DS;I+Qk)CP;dA0dsGIxvf6)9U&76pf zr^Z0C_|&N}3^@#V2yTK-vqs~JI4)xF7)zEV%{rP$#KchC@6gP$6pMlbpHD>Nn79h| zke5v3B4{BhN5Q*8@i15=7KN9cguqJ(EFnn};}MOCM2WrxV!A^ZC$PT^gTKNGpaZ;{ z+%XrTKxmFd2-<}dxAJ?+}5vODv;S6@xdrdB+C zOUiQp&o8GveQ8&p%JyaKuB%U6u*PC4nhZp&D{p0Da zPpIzQOJ3F0sj{7fY;x7=p6^}kT&)jeDz?m+Gxbe6{qd#dbp4+7`ks~go;&sXXODq4 z8odicH=lp!`Ni|g&FSs?R~z?V!?&KC8OS&)uO6R0KEL}$HKRLG;4?Uf$jy<5GIpzA)wW)=gw0p;zYscdD zHCI4o10dWRlV6)uE4Hs&TW?!i!6ok5s#W(>OUlxiTC+cGJD@TL9)bs<4S|isOcG}v zI>4_;3n(CSTq%BMJ2Ey?|%o+xcbc4b8N&t{gzc+Q)PM-mNLoeB1| z6z>`QoLwM?`*JRaJ0zIDfU-A7IWCxj)>6I^;;n*ZvwYH!h-U=`su622pLrM7;BCB} zcXZH3h7~Bvk&Ye6Lc%e~HVHHovUR8fiq;b7q9^5#eBvs8GE6M1w;-e)r410)be85T z_U6!cqqYN(Vf!mn-wy=1!?~;>!i`RGpPN*0V>mIvMG|2-5P@VVM!+Kgmv~5uc1(pP zVjVVEuP?#H6AC8+X!LN(xv0#ILH){w0gV7?iptwvI$em9MMy75p_|QPI&1_eY#V%- zSWE}!qi~alW{BZ>2ET>CF=W!kiB~?)gL!(lIhTF`vg!a0((3@tq#TeV0qp1nH%avN zb9g80u>rF&I1gY!7L0P3%@r_PYcLY8$R>H#Q}C`#5Q1R&eaL){(hm?CrjF2m=>2`? zLhll@cyVd)!!67GAJi>7ZauT+?T0f=p6AjPZwsZ_$X5WszPKW5)*K(Wjc{d_LdrQ= zvrLG}xdZ?e8EWjs5RQg`I8~WPTVo335HSERBz3VwGLmWqqXKlsBLNwQ17OK~8X0NN zn*>(!O@yWP?5jFuUgix{J4(C**2_O{*FRmz9VDUiQ*%lJE+$IT z%#aa?7!$i=;=~2u_OluzL;j{&&WWK2kwyPrqFJ&#-~mES&ICRT7p;sqYW7Avy4J10?WZEE<(Z>FNHAW6PXy{e^2UfZf1)t1pDq(J^&gUK>@^ukl>P@o&Z9K=zzO;}eefJTeJJB}!4|c3^LxKH zv0m$6srBEfeQee?!(_bMZ`Qq2_oIdtN8=1(;_8Xn6Z2gQ!p-4#hJSc=Nmw4b)&2Xa zKfe5jm(??pD!&8^zd{`%>&sa-g4AI$svn^L2|FL5ADU2e#{&~`@A{NQWlq(#ca7b< zTyu|gsIEP0?4G5he%rOic75pkb=xo7ZVBqpmsh(5_4Jvw?lbqTjKgvtF=h*X8nhIK zU%nB&>L4?wgUoRL!RE<-p-AS@-dwdLApva~nkk;!I}~R7#h6hX`#k|Qrr~G z7psML3|fKdx&)u$9@i=9AW2_K+At{_^ad-D5usx;e2y~(NmgCb z3Pn0lemxsX1v)PUunM2hNZzbB-ppHqEyk$O3;EDo4FAJEgCa;OpOVH~3-&(?)zw+3 zpZzQZ5jzx7dL0b)Ny^&Z4)sF#f@YJI1QtgWoD@l*f#R4vt}lsA#mJR9RDeMv>TwaM zP5{GjGR{d;5MJX?em*{S+w_v*`O`jUhWjxuk?*q8>&yD^Ujl z9+HXakyA%LjhP5REfs^wgYc981ea-a&snqX^xby)e&$|v?t{3-*wz{M3gezHTim5G z?p0=Q#$LH@Z&KGrBj_STb{mX$^=RMY`mXip#etLA} z^r#v>mp&a`E03;9^175-ky2~yOLturckK>6>S^rA2$6hb#P7f)%;B$+i(XTX!JXzL zPz~xIjY7pal{KAiM7_uxBE&>#WF1l@f(I~QOyxbKFQ3&d+dBb&3&g|&7y~JZkMc8c zxo@H<>M!WTN67UNvVVkZe?z`cEIX*DKN+Izl;x9#a>{wX88MET)T+soVXSWqyf!d9 Q^iAOoQw>^R>hvxC5BH5j#sB~S literal 0 HcmV?d00001 diff --git a/scripts/contracts/__pycache__/inventory.cpython-314.pyc b/scripts/contracts/__pycache__/inventory.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6ed2a49888974783ce886a844c466d6687dc3c3 GIT binary patch literal 2736 zcmai0O-vlg6|Vl78D?Pkx3R%24bBD{0~33#D9bvKu?SvR8z#+oZ0v#Ap6P;V)6?Cq zt^qSUagcH$H+)*hisa293P~B)gE^D5Cz0KMwN|}D3?`EICc^#m*myVv?1{- zDXHtddhdHxuimSA)n^)`5d`i1FaCJzCqab%%m;pxYRt1cz-%B5QM8COVM=&Yq+$=} z#VLtO3t}%?kk1QMbfVrjKcwetY@*>%=5veUP^Zr$L_-Uq*91-KK$bx3H1RZ2<r_9`W?j~V7`P2f!RDvOx-d|IyD_z z@p5@w(7h3u5-Y&N+QzVHE3+=PFCvfq2yz9jqZxf$m^2Yu0~tA;eu2Lx4jqYl!q3}bSRreE zetcS)YYZV*PRWVD5onF|bLIv#(G8~IY*bsK&gTwkFS>@0ASx&7P|LaVHJ_T%s)^QMHSz=U3-TNAt?^}?CVk^NLI%uv3Tg5cK?}Hzsc@p@OOQc-ziWVY zo7`r%CDnMu*VdP9dXW~=>QYU<&qGHbZqpIl_9KcK3%21@!+FRLZ*FT+NOV;BqrDbM zHfic8WP=yD4+AQsJ%RRk@S^ zxBbLF+X^~dx7)*Ty>ZMVxtQ*nC3$d!cqKUVHwTt6ExV2fT@gN==JI6&TbN=sJ&^VW z`vz_<_AQ)I`vyh^$JMl&UI3w6%qj!pqdMH`oT82|*~&nA?TiYV^nj1^1_3cV2C|Ao z9p~D2Sis@8m!Xo(9bv+FsrACZ&GZ^90r>PtdPE%`E6MMq&wjV`^K=$uao4;GwF0!>Vl6ij>8w${rD;@rYHdNV$JV}W=2aA{%CmwFo3 zjq)5dOy^3@jq`#S2lvYVJxCDK&Y7-mRkTw!#RL}gQEEENWC&fJ1q&ETlfgoH-8L*t zO5*4!3wo7$%R;Lr+Vo) z?;&>Igs%@mwKxW$8tYRfRU|STIG8>UK`~UZ)b6L+a*BqZYZl1yA*hel)DW{(OkU`N z)v?;d9VJlwnY?T{Sr#S`n+%2O&P0M)-I8fTcranY9JmN?feS8r6cn(WD!shGYgLscx3AZOn5OQywWi_tHb_yk!3=LG;w(| zbY2^DUsYae>_RN>6mV=6+{a#Q%^@qEtLJd68oQX61GlLc^WPgUcK95+>nvJLb*8!< zauych`|!Sid%A)4qfHx=&qdTTE$-HJZiYW^@4P?p_|(vou5-Ix=N^51uWR&4`x~1R z`)wWXj@=vEYdgI;aUi3P%i`vx&rfze?0V4k-nCy|{ME$!6Tdt8`c`0nV(R0|A71`w zX16)M6}*3AdwsvV_u>2x=ePSm>ppvrJX+n7_j|wc)9xR4Z+m;auib0hlDG8x$M!qA zew2N<^k8Y{)zJ?w{W`h#%Jk!o=`ESB(fOeB0HIek3Hqe8AU#8?x_{3mbiT$|syvA8pz*_nQOCwuEN%h_@1KM=Yt#Koso0#jWS zCqL;fgLAcHzj2`#E@1vDM*A9DwR38D^18iCgscWbA)E(gbe00@^$&I zRRX$snQ#b|@5(UQjQl-5>iJdxfGzRB^C;>ZeI7=~EvJO>`%baL9&P6nAh#1(tF=7NZ9ycB1BYe5`%p5VZXO@VCVQa(+Y&tF+vqfyj z>=FAhN5pZ=8F3zSMO??0MV6IwBEOQ0bDXb`<4QTCu1TfRuU_El6PI|SVLkh=a{Pkg+N%sI-{Fn)S6Nh- zI~0t_-rmR&Sq{l6FP=KpsaktO17WW$sg`gk;O`w)Ey`ezB1P1^>yqs68}BLI0N-RPCYH^ELgIG$% zuji_^s(fTPjIIgwye#!bRC`zsg(W%SmlW0L_o-HYFd|)%WYw%hyb+{RBCQwg%{yzi*KSjJ zf3Pp4nj`*5KvJ!J{-MYq2Fi+oQIPkk8EKHp9LDe}PxI@Lvo6w)3Mh47_4{tBd^n`w z{fg8(DElMBs=Y_TK+8>ok$yDAD|-i|2#P{ef=G)(R6d9}G^4j)8t|$PsxL;qIpiCr zd4k3UBTw_u9!wN3Rln|KB@}E?ZL*|Qxes9s#P`BZ`-aq z&j!7Cbx8I{(FJyzccCCkRMb4Zif8D>D24BjM8b)Bpgvl??{JvQCJcpQz|5;qkJR;t z2Bf;{-atT7>TZPOt4i40E7c8nWmLRQtASES^`=R$)J4!151Z~0e~&+aE~yO!?uX8zksD){@s$%*lNUa; z#wt5v;)PguFm~+*l6=*0gi;5ac)!1YL za+{74I&L~c=&b1ip_fd(g!)VYLP66FLXv5SP{24$=&CVF=oQm(9;l7)B6ObbCUlv< zLg;1w6+*A^N6kPd%pHV8^F>14<~~AxbC^)S`5Ga`{2HNV%W(@(n?)qlY3U}^YPm$H z$#R*{S1i{F4OwmyI&OZI(5w7wg#7#=D^M%nM96JzCv@7{Md*UHk5IogKqzRvK`3f% z6M#+$=LlU8x(T%lmk148FB3X%^$_w3eT4kNbwWcziw&q>I7+C)dW=xiaGcOJ!wEv& zhE_r+Z5P;`ZGceN_6nibY)9-sN9`vGowA=JbiwW=^s23gP{`Ix=p~zvkW(UPmh4Y3 z1-V|D-+I7w)Hv-nT)fV8aUxf*Ux|0p*;_4IEORv6YN^gwFr{AGzX1N+Kks;hUxspH?0g|P646Oe0K3u1_5ByhS zQ}Q7^DWa#8@pbNzqj2Q>v}5h)SL2Q?<459-9TN?4$AQ?vj=1A=OuQI(ycFYKTA;Zd zte7vexo99SZ$`0m+9tn9adTT{O>k!_G&?(vhU7`bUndk(}6J3D$;xfPjF%NLLm=Cx@EC4JNmjf1wD*%hdLco;} z;zcXLb0Z~UG45B1D*;!FC4g(hRe+`9YQQpa4d7a_6tG+@1FR6&0=^)Y1FjP*0N0Bz z0B#W10d5r6M>chFkT|RU|Vkp^WxbP&1{&^i-fI5qUu7P!N!DL)F zuRQ|`vFKMR`~rx%L+|fYbdpj~QZm2Dt;e)($D}7g1t}QtseEq;;!E&JQkY$tg3=9; zHm^v5zVvBthBkLLtd`7gSuJ})%t0aMfcdgw<+pd=;@=Q(@z#NF9T+S8pyd6M=@r$t z_}>%e&wo;+LGHqn!z_2IGqN`fvB?G@Ln4TruIyp}3=sN~3Ad=wP(QyxR6W+97M2ecPL3ra5&^Nq z zvW+NC=hT98Rw$VgN=6IcTm7BY-z^=JKNhOzw=_9)GV;F8)zMtU)!ZO*n-G<~#i9Z6 z`dTV2quvvjGXzyyuNX1VnqZ3NOF>^a1U0&1z^^D!UEA@5h8t=#>#yU%g*r=?21v8^ z$e}@~-#I%gBN?%Lg=7W9TIt$b{5OT$!pO=gVbw#y`AyetSFE^le8Uf_?^OTunu%-Q zulrbNoZoxNdGd8u4wepBGAuUzlM*mPNQE^HpRP6^wn^Yd=!jq>l=zhnQd^J8J-Z&a_7uB7$) zGF(xwAEFq>xVq^oE5(;*>uya&(fWJG68k$X&o;bG@9-UaCo5yy-ERmZg>RL-Su!oG z9^D@oD#r__gxY87@V8NDy0F8bS6RR;Za0 zYR1pc2)mzYm~VYa!wlkiW@(b4x1`CFwA3#~{MTD?M$EHoJC@FgigJc?qf-k-5D|31-H#!#lHOSSfdLIKGeWP(aHOGB`W zB?;^hxn=r4e9`cw~yn z%DAw7VpCjbm=z9935V{ko)OxfiG%)(6->ljYh3bJ&(X)z?Mxz1+PTQpW@`7uJmv&l z9EyvU_M8e2BWTs&RSi8UJ)g~sq?}AGnsd*Fvczm)7Jd&KKM*SRFM65Pep~J#&z^&3 zph8>&M7{sTyDKIe=j-`#0k*>_VJ{L)?pomSv#bTKvsca=p@q~_!J&D4bnyioV*CWB{IL39$* zHZiM=as^)9F5d*4XDlgY)0g@RM(o6HRI0z~a?Rcx8P4Dp%4Ps`S#?cXe~q4)5;i|m zcg?U;mdtxHH=gl=b^28q&rzR$50kqq*k0xfaNT&-99Dkc*gM}8xbQ{lC88mn$2L}oix{Lt2~0_tpfiLK1&~`R!lN2epM^>gWx9- zj2gXLm>r`1j!|QE0v^ws=9-(qAav(&I3!14d>};{oeL|sr?=l5ydo*Ampvdd1)dPi zx%+jgWk{%qF26M8=^YE)3WQks_KB9sV|V?r(`RElyJF|P(a94i96O9lf0wq9wM-^|u^+N`8h-X3OpPS`aQYm;Con0%%$PIAx6 zWYXmNbAe)L>6E1dOD1;5DjQ?v`zHJD_QwvKik*IGx}^JK;nMsLw4($z6Kk+#L>nsF zEJN3zb3|S=W44iJ6#Sy8fn)Y9Z}1z)+`ndc3_?Vcmmz5R96(*A6{3?_f?&SyFh11FQ#K%f)_L0$tZRnqJJi*GSj*jgICOj$Z0ZJ&;bq zCjT+?_&3}>XFRXL3Z{zL9d72TXp=o zBhOh}6_mxdX3*nN#B-j5{#9Gap3_D&&AbrhHI&z`ga+ka z$)ogpRY6avI>3!Q39n1sc!%^GnKs72Oa2MvE$)+ICZQh8PZvu|)9tf&?VjLs*i4rbKw^bCU|s@8t5(jV~m$UmTbwti{I z=Z78xmsl-T1ct;a2ji)#%?D228wtt7sxy6GSk2R!yWW*NHB$b7iZmfkzQZcjGZ^%t zCwOAO@&-gHH2pUrRA@ot)b`Y5P|LC~L;$=ZTQe1yU-oG{BkT1`4kLJa}0GGTZvB>w;6A>Jj^Q@JwB7S z@nL@PTdp@ww{>pYOFdiVi|dDDt|LM z;xvA%akOuG#m3ncHB&2UrdQP7I{eU8Ji2d8imj@NyJ}{g4O7mB$qh5k7ax{y9IqN* zGjSxgxpDH?A7A;?;Qe6i?1kx;i?c1hsTSXKOJA)2T1>f)iVbV-rAIZF)$$JGt;4^2 z>X$3I@;<|-C0zch(S7l}P3WV9LW!OEx9wx~(8OCa*WposghT zMV>%^$^VqVzXHIjUlk(KK$!8V<(;?+VgG3OuuknVjfi}o0CWM4Es@k^n*P-u(mbE; zm^2>2Q$e)Js|*Gr3MB_|YjnLYu{70|N*Urz?R{s zn54wL&{W{MfwWrqyP-tV0CgQnr>+I4F=Nj}PhZY;W~G>J^7CL}pW!rrg7d8>tqqzF zyiq|f8T6B%Tr{CtMCw!@YjJ}rt1R!IA@rWQZ&XZ< zfR$${_94LU(UVl&PfZEi3|B6)YRf7DdlNT>3-bm}h$9ndzcV#;IqW zrBlw*(XO$!c-i(D=Z*yPe$V|~_f&c3-Q6Ene(XLoUEcYg`#tyDnNicFoiom}85vH& zN_76?gYDDhr}PYIQPZWTW}F>!nK(ILzuQ!ESTNsr?E-vYDj~SG0`QLo$Kh2u*!pLz zxxblg9ZPBa(pVBU9TrhmkRrj7cx}yPkpDe;fU%`^c#q|pzVahVHGi^hWlDjr>1gIy ze-KLs*q*Su3=DfheMy_Rq8`e{V3u0K2&$2Ad09-6h@C!9bM(S=(Z#HD^mB8j8bJE( zy9b*#neT0KH0{YTU51`pABXmu)X4G1M95}L)ERx)nC&Sv?=+2kAtz6!ji;qezLOLm z7T`uotPf%S3Zk6rPFhO3jC!3Q4~_Ion|$-`7n_W2tF@`P$drYLKw`?8q5?QJTg*q- zB>dbf`uz%)$k@3KR6x%8`|yQgCLg|!W-&z?%k*HtAH1p-q}wt@e0Vyf88nGw!)=}! zH2)Lj+5nKroGaLTPrTccdbdY^x4JXe6kj0I@6&st09hFCLo8fheDlTc)sOT4xRI?{ zAGr!fHvI0z(dxKs!>qGr%2_kxtcB%rwqWyA!RCMAitXu~EjSx5IQuBSe2kyT-w12v z@-D;YR?PP)SM}!>x~C>$^>>nk&us_Bfl1Z1+t9uJ0E)nm4-cSFr0! zM{`3Cy7-IVAi797P0ye0VN;hW+e#NRJ)w4yew@j6v#&=}+BG)E7NRcN8)G4Zy5_uN z?mNXrS$$jEQmCtK>rztD+Y&Trj=FU9k`bajgu1JSiuP#!nPW{`VREA{P?)z(l`T!2 zq^OXmZ*o*Kvvt58kp_^O8j|214?=N^MVsd^;Fkr7&Y!k{oZZj-D}PF)-7+| zYr9v!3D@^mJKYZ3{Y?U0?{&BZ*8>w>KX5qQYg`YC+4Wk7yDkTn{_NL7rHfhdjoIuu zy475#yD$z#=rYZUZ00-((=DUwES-fWRfg=@-pS6{2!2L4!t%qsi_}k5R!GXzo*w542B{e zIw=E(GvM1Z7^gyTMUzuBeKMCk{X)fkk19u-l}nSf38S&^z2J9((`##|SJc5|T)q9) z;gQvGtamO6)gEVe^ z%3>1I_z9E7&CA?n=KD2Hx7~JsuYl_Zc87bl>p>B_E_1kR|396l?~47uN9T^(+B#h& zX2xatUt(@YSNY+K?vpA&Nfop;1gcN$B(=+*B7uzk?zuFot>m;lBM%*7q7x^MAn}GJ z50&FdmXmuWx6~58PgVFw0M9{*>p+epMc-&0wZJUAI3bF&1!v*~XVM9=GlLL2L5OEE z2vH{Y#SAl4(jYwZ_duBb9g9!n7Z9J%uLv(~nVC{?dFBDg_M)Tti5>BzQk$EAs+o{W zZp~^t={gbhU@JT+q^ndR#}TewD&#Bi}&=)j1*A0%!Ur zZqDNg$YaltW+1{2#UiF2q)4c+%Vbit+n=tRfBIB!x>E=ao_cEEiqA!~v(=(STem`| z^!dph1FkK)PYP|mMpvXiOX$ON_7_h7iWa9o(|lL9!h@s}TPDjO{^-0g56(si4@Tf7 z2YonNEDgcZR1xuBxgz;0lJAsNyh*L>Sz@Tl&!&T`CS_Q`#?4?44m|6X6r2Vo4+d4c zG=vk#BIMGE6cJe_J0knQP_@evs>u9J zhxvqPtLtryktLg?N&W$R%!bcT`>@~+!mmebw@2$K)t!r-JwqWsp_=>@U`hJGC(hzW zMdfc@{?_HOGap=j|MCyd{J1c7$QN($P4ALoSCpB-q1f=v>A_c_?lc+ClmEy?V=p1e z7$x+I(M^YQ9Wh-ZCOus>dZE_VJxy#C^h^Cg`#X6!e+P3|q`2U{HO{h3UV zJTw>+jIpi&;RDYN+0IQ#Z45H9r!k!qx-EKyPRR4^Uo0=FICDaM;vVbQ=OJsOT!Mn= ziv*ptU)f^df}?35WD~d?9$c(gHFJi*C}5ft^T~drm$> zA+#H!>;r(q6TEkDz{xkAx1A$hGeYS@SHY}n?UZZnJHxZCs<^9)1nG&}C*Ej-5oj5F z3##z8|TZ3~b6b9u)^`3+zo z(F!_~P-(2I0|zBP;96ogIAxHIO4Lk>LPt@;de|euIg$(y!ye|N?8SBh++reh5TeK1 z%lL0sE(Z5}%Vt0w)4cSt2ORw&6sd7 zA-G!c_mQhrb7;B86*R3e-&^Bos>z&kq}?zE^XoAN{(7~nT4sC`&0kQnN;{GYq>@Fm zdQQD6!wFkw($C3qt(qn!|1A;6{{iTf4J7&h1ee*>51DBO7D{9#AP}$-Afck2Xke$? zZn{ls8nT1poB(qb3e6KumT+c*B6+SrHScD{l&Q zR{j~lm#b#PHqc3?r?nGCyKzn_X%1d;SS7DD%{gL@<^bomgwgQ&2RGlpIbFJIA`mZi z&o~cfRT!FgkpXbN(Jx8Qe}$fZJ_j|-X&T`bq6ClDZT zjldv*D1lcA{1$<)6Zi)NzCqwy1ZZPaexCr_w*4=3MH;I7-wFH`f&WPWOpcTPn!x`i z;6h{M0s@5uRuWhZpcZ*N^bxwh*Ml!WWq%JgdK9#;z=KaWp}G5TRz|NB2zWgB^vsnO ziEPNu9b)TJ`7phMKpFm(9|QdL?_f$iHX6-a9t#HZuE!Pwt}I6LzQ=;myeS=+aBFAj zOh|$&JWa%9q~py8A3KczD9>l00xtua%xgch=bH;3Z!nuL8Xhk*n@<&D^AW@6`4)4F;Zu$vbNXk^q0t|}SFLFNO8FQya*_ZE zR`$&-`(A`hK{Aucv?x)9!Jr=>)4~eyeR{wXVA7e^caE?#7Wq|*UEBS;g z_=L;D-_N;ov)nlhgF&)Cwi^r$=u4dXLhzj7GojgJXfceI%&w}QT2=iS$8JA8Vc~e^ bt>}zt)kEI-J14((^6RI5Y2x_RtRDXtDmL6U literal 0 HcmV?d00001 diff --git a/scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc b/scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a19fddea072ca4b8bad322d2f694579575bfd399 GIT binary patch literal 7690 zcmb_hTW}NC89uAsl~%T7TfSfeHWJ|yWWc82m^%amu1$(@7GcuFuvtrMydX=;*%ii~ zbjswRF>PnUluqjgrliwO2ReP=r87*QfG09}v5_xy1Jh0~)5#l!%+T=E|3AAcSw?|` zva+$|NpM72$m7Je*dq(ovaHG@(;|om*^xOJ%Gena-7IqH!Cs~KI@5cme8x3LvR-}su>@O@>XzfI%QHd zZtkZvO)Wt_bSP#C$+VilTJa$*ehL!)6Ix26s;MQS9!oGXTHKPTW@OSSLxW=PX^rZG zqj7f-c;}qVqh3g;u;#~z$;rg@9IrC@MI;R)t0$%)dq9dZ57H|OAbqk3Qj)zO{jv{a znJj@Um;Gj-kC;KZYz2vyTfttMPGsX6jcJBy#NF57TLt)sMUx&SAaR!T=gZus8SxN8 ztNTc=v$WXAz52Ko(gx*tg$dMgp<_Q{vd`1z^a+bhU^rY)l&2DuS>>u>Xw=;QURF(7 zWp*Y;v)WZ2)=f(eT3!to+OUKZYQ`{d0IMSrtE+V=J*>5!R+CB1XdOw@Q$|LOYpug7 zrP_O~Mx5#y(`b!5n`N|`Fl)ulDrP#J)Ke!~GNaZuR+7$WDK(=jL#m-bpNf&r(zvG3 zbQX#gHI-2EJF2ME)CU0phSgQHrY$etQwXMIyb9th`DcCOg^_cCaqnFzFef!kNevT= zKB=CTRzm)x%5#+y!mQNv2&$5zJ;cRid$<8s+Ev&tF2li|a0QTOJpK9okv)ALnP0aA z`?4=t*zD}&c2424o-D7-Q;&Y1TdxQgZ86g8cS{QQenu{s-_E@$93Y8CR6^K)fNLLL zmG8F`s0U|`*9n+GBYC5okYz;n9t`>M?mHL+gET&xa|wXa!(J`4p7a^(v$))%xXp3 zfjHd+|Bu#zcm-Z|mc&RWEJ+4*x{uomPtTCEVwk)E(`VBJ?s<)ep?JntB+~Khu$BV$ zh%f*~hIPXLxJL9;WI&Cd(o%_@tK_Q3^0aKQL|Z>_{Hr5$5!@{Q4g~9&4BcfND}m5L z_cp#2r+a0PriT>(HmIVj0J@=2?W7imv9#<+rsHbT*wvEn9=4Pii$Qz=^P3FEr}_sb z55F}T*?+rkHqw*pANcB#0Hq&P*K)DWo~YOI>#5T)u{0f}*p^}`;{l2qVDV~d^r}c} z@F|vCURwbv5IC_o#X#(W`bV6J?qC}Cg7dKsyu7ukhjAJ2f;da=OKblUT6Q5k?!6bR zo~&7O?ez75KaI@=J7$9&_Zpfm`p$Kacis(DeRSyDq04(O?ww-GmJ7!(@4dYD<3cvq z7@rO#9*d-6F9)sR?@qAeiGl0%hqewuyt#Za$Xh~rN1eY2dU+-=pJIZ3TyJ*udfimv zzPyMjT+PaOWKVxyzwzMd^Axn#ttAVqp`(7#T>Fwm;1=&Q*(B)JeTq#g-IBt+zsgN7 zU=uj-z7d;94x9KHn=EMM&ceL|H=FB0lEOd~sN@n!x@zq|xAzhQ}cd-twJRsjj9>utAdPY@&z00)0n4 zbR`Z#wAE-HM^M}@tG|Q>u;vaisPVX-0DXlMaa4f=av-TClypjg6B{fT<=N!09Nu7{ z7Bg}PbpFPBK6}kS)4c8aiK*uOA07Gek>7g1G7zmFR7SX8ltnm-j7ghNgh7;Evlk%L zr3o#QOphX^Mx13rN*zX%&R!_G0^VmVTp!vFPjLYaSZ{~p-kFNMefzb^$j%!;ahA&Ar%_1R#3jVjU|UEDP0O&90DNpNpgW4jD_sj>A+K#`y!K){VRgT>O-Ow2R?gV!|F#z=BW}vNOwRnqPjB<5($L6o$y8z?6g7qqc z$t?R?Iob0}9ztzoR}l|YgH@Bal-wG?aCzQbz(dH3T!9zA)~XR49ugT3EokNPk9!9m z^5SnE4|$)$aA;{712Oj$Tm#mPup*-nvAqv7iS|oKWopcto8cf z|9M4)Qc4ZZ4YOg;Y@zFMEC@0%Tx|QSovHV?o~O0%Wvmtwzi|Y_S;CaH`TDxs(&W}R z=YsNVP%f>puztC)>?*9C%Qec=fy1b;hrvFP;qOjRw(XONwmlogo0W@EZY=NMi}e*X z2$n;7^mjIhVtw5VI*ivdJRg>%j=ja}w1@w3vAk5NqIpXM53kf-Q471=m>&G<8WPeC)> zlj-RvbUC_{GKX5-pVx#!yhZlRw)_fz!g+*7^elaBMSR5qaRSl4kkr&)Xus5Uv3>mT-F4p?=O+VerljaSsp@>qPlD%9&PdDWBzUssO6|1N;vnZz z#V1CtVZ*hCnTBn%()MQnWZ42!ryosMc7X$tcfv@X#xh(X?9<1&+Xb#27)Pxf0BAuN zX#VEPVo}apl=C6*zEyi^e*02zlYQ>^SU557dO8!t_st7p6=vf?6KK~DAS@eO3V{Km zHKCo>k`Ugbtxk-uWmFwbT7o&6(JY@1QI-=LwaO5k=@fVfhUGH=bHl1(`8%?DGSQtH zOj~6cihY>CcUAykmxmHP00Tn&qT*20V2UHQylMtQiV3U24r%tJ?GUGnU&#$xAli*= z(k#aDT8867$Pot%Tnr&kJ4j;=Ax+zXXlrW;?0&PR=-(J4AQ)aR36tR+GYva(H9N

3rjiv}|H@Mv7ipJ0rDUdw)jScjNe!6uS$!UQjkR zBX!J4-BVKc?Urfj`z)HdbV^z}(fPaXD?_=Jn`fmhPa_=Hju{6BY=lGBzIyWrcb^dW z4&V~w)8Wd}h%eTT3Bi){&`0J z_m1rMZvErPD!Pr4C>S{@$^ik6@_A~lA?u1x1-pD;=3sD!xKG@VhDs)rznOA+Ynb& z6AZlp_9aBILW&Yk!iFdGxEa^rQ;7oRZE)U0Xog1o>&(}o71$pN4B#0^IRE_+mi3`W z5SKj+h@!?ltniA*xQA7|7~>w6@nRoHLEM3bTOU`5V$%Zxf>8zQ2xmR4>M3~fV!8!7 zv;+jSiyIO8C1^!v7|lrpTuIr2anHc%Ll$D_MV^to+iuk!w2N qHCMZ8s&>_v1Tqg!azqG>k4^K7?+Sqr4}Ng)M~D8y6JZH^@&5o2@e>#T literal 0 HcmV?d00001 diff --git a/scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc b/scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d06712bbe1eb7a3a07cf21c3206980cd04ab2de GIT binary patch literal 8050 zcmb_heQX;?cAw?)OZ=w3zi3gGY)jS$C0n*^*|A|opRq+VYnf7PO7x0cN$XJLdUq*V z^cfNImy%v;rI%imONuCH(b~OB<3s+aTcD`ZT&_B_#Vt)KR5m&!=m*-Oe>AaET%G>Y zH%l%_>4c^zI)Y|qXWq=bdGF24?>)BItR@60{x4r%|JaGpKj05F8M2ipuM!9?Aqt6T z5>do3AreZd5j9FliX@bpVeODk)YWBZ^rBwrGl&MIG>S$j$>E|QlV}<;i)MvRJ8T)U ziq;{UXdAMN_92Jp7;=ivIz;IzkXTIVAupLIr3@#=5ehFMREuZNtu#US4y(-O%X&0{ zC}TJ9jFVVCQSS3(>V-K~G@+wR-I`2W9RjM(!v0LXE88SiO%%htRX(+$fV4|psb-?& zyJ#kADDy!WJ5dXidS6e$G}$V&*Gy_Ci^`cgJII7+Q2v{(iTC_;gbmXojX&z%|A?jDZLLr08XY>P!Bi|!c5WCmyGLa{i^7j_E_M?*UkS!_3X zn+aL#qs7@~ja=Ht)6?RSjw2nNN3x8^c-fw<0uTK>ALE;KvX;7daa6WG8;goO9TJsh zSsRJbVcC>lQ#QaxW083#loh-pIS1q<{6F~~nC%h@gaS5|%7^#4E7c=EsqV(BIe!G9BD4>2S`LooBnp(MEy6PkaM`6!i>yG`` zchgW#l{Z!2oWt*!hR3x~LVg=aq}Xo<8RX6`$iPUI6!O41{f@o(ph`VRoqf;#vhV0G zS9#=${l!d;!dve7^B1eVzN2}Wt2KXae|2yg_FTO}9nXAU(Td%7OdXvssY;b9r$f*b z=x}e1SAEBh{{JLW;xC!D@1d>gBN~-dvZc3CBW$C(O(<5&Ud> zAdAu*+rCK8MP$9e@be5W>*pA8HWp441#VrPygktpxSh~lo%Bq!Bs76rGI0xv+cI%m z)^dO%Wa8|O*Io-;W7~W(1lfL62QU688i3s?w??s_@MX`nx;tD_xi|kNS^}VoUsn85 zVqkTQWQk@CkM9l)U33bjX(nY&dxuPLG7%K;18~somis)ju{p*wPe&q*;8}?AFA5wT zVmxy+50J|vgm{({1y3joVgwItmYrS<0;Gv%I@`HLx#;u^6J>64{293_L(qOY5(@$J zI@6x(!tbJhAHRQv{YkE~KV7&d-o3F(zdQ5R%v$K($ZF)ng{K0R#mnyfM52M7n__3; zvAE!l@$NZR5ZLHU9;~^ip;ac_iISUb8D`t!fa`!cmI;GeL|Tjsa#0RN%7!pAO~)f* z!qwxBM?t6YIY5B`^Z=X10d{6axMia{L^j}E15Ls~b6kWCDG1YZU5G_t{?IHlM`Q4& z;8K}5BAc`I-dK1sQL}52=Pq6JsvtJewga)|Bz57(Mdqd`n*`92 z7-6Fz9GxJ7-O5@;S2_&21(_5WQP!~{GY9=~4ow;28=I5$Cu)% zh&fpk<79)f1=zY7PYs74#EOfu72h@>o;lcx$nf|C@&Jc`fhjOjz7AJyS+fmhO&aX~ zMx4c58M22?CbAQ#Q}z=}LtCvKA9(M1Q>~}dt%J$d!Hw48l!4$4P_kXwYCN!Z<0lP2KCn8vG`L+{wXEN2JNCiky~&NXXZ{1Bk}#QU zd*-dgTk~tg8voZ7A60x@wLUFfp4jwGCeKa2(Y+C3(xKR+P;7(a)7<AR2a~l^gX?jUzmS=HsgXklnae%ylixKh?2|E}dM@d+HfV@J&3b=s2tK=jeFhB(Pz!OZNuf zJGRz&|Hhv#{B+@g@Zt9!-b!{;Z!D}5KUlaoxICCFzqD<(Eie4m>DA5+bM1C)eUE3D-T6xOS zl(uvxEuHrpH!R)Iai`~%o)tD_c0V$=-Y-vg_9i=fA0AD24nFD}+;Y{u>wn9?Hgf++ z%5^;L>PfnK9-MqQm2wTIT_Z`?h~&K{O@&h~ChcOAE>^nEr(D9acgya2WIwQ7-mrFi zEhZiFNsZ5^$|=b~VHWnKEFEdf$)x4vgM$yNHha!*ST1ZC?JJ$HS(Xi3M#~-JE5?g%JZr226eat!L=5ZTICOJ9pE4Sr#;ZQE+3edqsnK)Q70Z!Svb z15)io$~Gw(C$}v1Yu!@oz`9K`k4f6Gr;pDO$bOZ0`i&l&%F`!$R53~j`1bzDQsHgY ze$-SA`Cql{_jZ~-F773u_@vSR`Oh`g-V?^3*Bc<;0b=ld498yk#H}2D2rldaT2vR$ zhcC=sN$mrdk0=N(IDPoe-<4Ea6$%yv7x2xjBp-ezb|rA42^AO?1Q#?M(T+HhvN_NI z08maF$U#n@W!Dt&3A5xNB@RAN8YQCVR^TYU;FUy?;MVH)aBKCGxBPlaTL`2n9e#{Q zIs*C85G~w$G^|G`>j7ujNu&%rW-=Plh(%dpS36!I;K=ZXkPlzfyHb751wTauK?iX% zm}}t-fijicZ%}0j;RCbUssvw*dxcOaldmdIrPdU;e-HUkrdE{%N_C|=RD%NG+GUd8 zm~Ri@7fngvsB3q^eGG{GWDe>REOMhjF1VscVc1hG0Paz8uaL=T&Y`bEE;OQL(J9h} zSfU2pfBdJ0QNVE_&cm#3)Piyr2_dCg#Da_Ag8A_u&~%{e49)h$U>`FF@l62IO5hVC zEF8d$D=-&u03YWtIt|4*7Gj{WX%(kV0Z3#ZA_@bi;hAs$wvmU4=7Dr34gpI*tWZ@k ztQ!y;ARTe>TfmE<*c{*ib}GWSN1pYmXmrOR#_0Pf{6F~v$a0>@0K8L9h&N*VkWCpA z%pN;1B|otf(Q)6U7(*V9KYA)K;^`p6Czv_5Hsw>FQ-IHhZ}G&Q?< zjZI(Ulh=4jcnO|snmDI<*ZCvo?+-o|@S%Fye$GMs&T+1!c@5} z!s&qEfx^R4s6aI(o4g-~djb8Cs4?p+O zp$E_Zr0av8Kkm6tfAGxRXFfiZ^;a7X5Q!#rOK@Z%;vb8+7ihsPu;49-@GAoy3NiRV z_qe^yB#(EIsNQiTb4ONOgcCaf1kFw!>zT)o9e)gGm~FlrGFc1v!Xow{7*45a!TlCx zBZm6Zu%ui(3bhzd^NW3e^Yu{w7eJfPaAo$gqUI{6D${7TZ$)=T2c+w_RNRX4~(ytr=3qO@N`y4ym+#!`!}I zS_yDkIk8Wa67sTM&2s-%Q_FJyO7o_rc6IEL<-k9dRwl7+rz*XvQZIDdOQp&)8}>6> zrB$nkH%kvl2fLG{C%)|NSm+t#wQwds+yDQ!KxVLg18-L9-%ec^4p zbhu};vS)dC%TkuMxRU_+O8=+$s&snMS*ACnwoUmrrnBJoudvN)G6Jbm1TLEL|S(}+qNzCnbwam!Km zrsZ|Z>g7)z4PTMa@a>kP;n!AVYXPP*e0?jN0#UzL(QnlLMKxp}H=iT=PZ@sJV(z!< ze|Fpk^-rv|{jEixG;46Zg~0Vz64y_e2drk<7!1zQY%~~zo7Ef!e;LP!;8e?|d{hYk zV1^A@uI3s1v@zk}Z|EIp^lTLWNY}hiHUxtZlL-c8BhAmiFK-Z6yAFKvbCAgf?8ipf zDaC{{rvPsPeD6g;HsLEITAb>_r92k8(?Ud2_xhp2pQ|g28g#6rRNx6 z!FGtmAkH^YAQQlYtD3 z4Qh(NY&<;|3&$hO8UDAS4Z~yseuzS#FoO76OA^{AMnpJ1N9NCw{a2_`sT6&VO#h5d ze}RTJ(a>Xk12IlKzCctG&aaLmtz|i}L00`rTf2pAudGYm&DQn}>z-KL|AB_HG@Xvy&mXW0a n$$TiOZTd`GwA^^-@T-Sc{A=enOb0*J9{SpjwAK7;!18|s7eV`8 literal 0 HcmV?d00001 diff --git a/scripts/contracts/bundle.py b/scripts/contracts/bundle.py deleted file mode 100644 index 0414620e..00000000 --- a/scripts/contracts/bundle.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""Export the exact checked-out component's contract for retention with its image. - -This records evidence; deployment verification still rejects unverified operations. -Never use this to manufacture contracts for older releases from current sources. -""" -import argparse -import json -import subprocess -import sys - -from compatibility import ROOT, ContractError, digest, load, policy, version -from check import check - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--component", choices=["backend", "frontend", "mcp"], required=True) - parser.add_argument("--version", required=True) - parser.add_argument("--variant", choices=["server", "cloud"]) - parser.add_argument("--output", required=True) - args = parser.parse_args() - version(args.version) - errors, gaps = check() - if errors: - raise ContractError("; ".join(errors)) - if args.component == "backend" and not args.variant: - raise ContractError("backend bundle requires --variant") - sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip() - # Verify tracked contract inputs match the commit whose identity is exported. - paths = list(load(ROOT / "docs/developer/api-contract-source-review.json")) + ["docs/developer/openapi.yaml", "docs/developer/api-contract-source-review.json"] - tracked = subprocess.run(["git", "ls-files", "--error-unmatch", "--", *paths], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - if tracked.returncode: - raise ContractError("commit all audited source files before exporting a source-bound bundle") - result = subprocess.run(["git", "diff", "--quiet", "HEAD", "--", *paths], cwd=ROOT) - if result.returncode: - raise ContractError("commit audited contract inputs before exporting a source-bound bundle") - contract = load(ROOT / "docs/developer/openapi.yaml") - # Until consumer call coverage is complete, use the entire backend surface. - # This is conservative and documented; never silently omit dependencies. - bundle = {"format": "marreq.contract.v1", "component": args.component, "version": args.version, "source_sha": sha, "contract": contract} - if args.component == "backend": - bundle.update(variant=args.variant, frontend_compatibility=load(ROOT / "marreq-core/frontend_compatibility.json")) - else: - directory = "mcp-server" if args.component == "mcp" else "frontend" - bundle["compatibility"] = load(ROOT / directory / "compatibility.json") - policy(bundle["compatibility"]) - from pathlib import Path - Path(args.output).write_text(json.dumps(bundle, indent=2) + "\n") - print(json.dumps({"file": args.output, "sha256": digest(args.output), "version": args.version, "source_sha": sha})) - return 0 - - -if __name__ == "__main__": - try: - sys.exit(main()) - except (ContractError, OSError, ValueError) as error: - print(str(error), file=sys.stderr) - sys.exit(1) diff --git a/scripts/contracts/check.py b/scripts/contracts/check.py deleted file mode 100644 index 8b98ceeb..00000000 --- a/scripts/contracts/check.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -"""Validate OpenAPI, route coverage, compatibility metadata, and review evidence.""" -import argparse -import hashlib -import json -import sys - -from openapi_spec_validator import validate -from compatibility import ROOT, ContractError, load, policy, version, resolve -from generate import generated -from inventory import source_routes - - -def source_evidence(): - files = set() - for directory in ("marreq-core/src", "marreq-server/src", "marreq-cloud/src", "frontend/src/api", "mcp-server/src"): - files.update(file for file in (ROOT / directory).rglob("*") if file.suffix in (".ts", ".rs") and "__tests__" not in file.parts and not file.name.endswith(".test.ts") and file.name != "generated-contract.ts") - # Direct frontend calls outside the centralized API modules are still - # dependencies, including binary downloads/imports and OAuth navigation. - for file in (ROOT / "frontend/src").rglob("*"): - if file.suffix in (".ts", ".tsx") and "__tests__" not in file.parts: - content = file.read_text() - if "/api/" in content or "fetch(" in content: - files.add(file) - files.update(ROOT / name for name in ("marreq-core/src/app.rs", "marreq-core/src/build_info.rs", "marreq-core/src/api/mod.rs", "marreq-cloud/src/routes.rs", "marreq-cloud/src/deployment.rs", "marreq-server/src/deployment.rs", "marreq-server/src/routes.rs", "marreq-server/src/main.rs", "marreq-cloud/src/main.rs", "frontend/compatibility.json", "marreq-core/frontend_compatibility.json", "mcp-server/compatibility.json")) - return {str(file.relative_to(ROOT)): hashlib.sha256(file.read_bytes()).hexdigest() for file in sorted(files) if file.is_file()} - - -def check(require_complete=False): - spec = load(ROOT / "docs/developer/openapi.yaml") - resolve(spec, spec) - validate(spec) - for component in ("frontend", "mcp-server"): - policy(load(ROOT / component / "compatibility.json")) - backend_range = load(ROOT / "marreq-core/frontend_compatibility.json") - if set(backend_range) != {"min_version", "max_version"} or version(backend_range["min_version"]) > version(backend_range["max_version"]): - raise ContractError("invalid backend frontend compatibility range") - errors = [] - source = {(r["path"], r["method"]): r for r in source_routes()} - documented = {(p, m): op for p, item in spec["paths"].items() for m, op in item.items()} - for key in source.keys() - documented.keys(): - errors.append(f"undocumented backend route: {key}") - for key in documented.keys() - source.keys(): - errors.append(f"documented route missing in backend: {key}") - for key in source.keys() & documented.keys(): - variants = [source[key]["variant"]] if source[key]["variant"] != "shared" else ["server", "cloud"] - if documented[key].get("x-marreq-variants") != variants: - errors.append(f"deployment variant mismatch: {key}") - if documented[key].get("x-marreq-guards") != source[key]["guards"]: - errors.append(f"authentication guard mismatch: {key}") - if documented[key].get("x-marreq-handler") != source[key]["handler"]: - errors.append(f"handler mismatch: {key}") - for path, content in generated(spec).items(): - if not path.exists() or path.read_text() != content: - errors.append(f"generated artifact drift: {path.relative_to(ROOT)}; run scripts/contracts/generate.py") - lock = ROOT / "docs/developer/api-contract-source-review.json" - if not lock.exists() or load(lock) != source_evidence(): - errors.append("API source changed since contract review. Audit wire behavior, update OpenAPI/tests, then run check.py --record-source-review. This acknowledgement is not a substitute for schema or integration tests.") - gaps = [f"{m.upper()} {p}" for (p, m), op in documented.items() if op.get("x-marreq-unverified")] - if require_complete and spec.get("x-marreq-contract-complete") is not True: - errors.append("producer contract is explicitly incomplete; finish the P0 audit before publication") - if require_complete and gaps: - errors.extend(f"unverified wire contract: {gap}" for gap in gaps) - return errors, gaps - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--record-source-review", action="store_true", help="Explicitly acknowledge source audit; review resulting diff") - parser.add_argument("--require-complete", action="store_true", help="Release safety: fail on inventory-only operations") - args = parser.parse_args() - if args.record_source_review: - (ROOT / "docs/developer/api-contract-source-review.json").write_text(json.dumps(source_evidence(), indent=2) + "\n") - errors, gaps = check(args.require_complete) - print(json.dumps({"valid": not errors, "unverified_operations": len(gaps), "errors": errors}, indent=2)) - return 1 if errors else 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/contracts/check_baseline.py b/scripts/contracts/check_baseline.py deleted file mode 100644 index 4c34a673..00000000 --- a/scripts/contracts/check_baseline.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -"""Reject breaking changes to audited operations in the PR base contract.""" -import copy -import json -from pathlib import Path -import sys - -from compatibility import ROOT, compare, load - - -def check(previous, proposed): - if "x-marreq-contract-complete" not in previous: - # The pre-bootstrap document is explicitly partial and has a different - # /api server base. It is not evidence for a historical consumer release. - return {"compatible": None, "bootstrap": True, "errors": [], "reason": "Previous OpenAPI predates verified contract inventory; no historical compatibility established."} - audited = copy.deepcopy(previous) - audited["paths"] = {path: {method: op for method, op in item.items() if not op.get("x-marreq-unverified")} for path, item in previous["paths"].items()} - audited["paths"] = {path: item for path, item in audited["paths"].items() if item} - errors = sorted(set(error for variant in ("server", "cloud") for error in compare(audited, proposed, variant))) - return {"compatible": not errors, "errors": errors} - - -if __name__ == "__main__": - result = check(load(Path(sys.argv[1])), load(ROOT / "docs/developer/openapi.yaml")) - print(json.dumps(result, indent=2)) - sys.exit(1 if result["errors"] else 0) diff --git a/scripts/contracts/compatibility.py b/scripts/contracts/compatibility.py deleted file mode 100644 index ef89be12..00000000 --- a/scripts/contracts/compatibility.py +++ /dev/null @@ -1,294 +0,0 @@ -#!/usr/bin/env python3 -"""Fail-closed, offline compatibility checks for immutable component bundles. - -The deployment plan is an input, never inferred from package versions or main. -Only local files are read; artifact downloading/authentication belongs to CI/CD. -""" -from __future__ import annotations - -import argparse -import copy -import hashlib -import json -from pathlib import Path -import re -import sys - -import yaml -from openapi_spec_validator import validate as validate_spec - -ROOT = Path(__file__).resolve().parents[2] -METHODS = {"get", "post", "put", "patch", "delete", "head", "options"} - - -class ContractError(ValueError): - pass - - -def load(path): - with Path(path).open() as stream: - return yaml.safe_load(stream) - - -def version(value): - # No coercion, wildcards, prereleases, or lexicographic comparison. Release - # candidates need their own explicitly verified deployment plan. - if not isinstance(value, str) or not re.fullmatch(r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)", value): - raise ContractError(f"expected stable SemVer X.Y.Z, got {value!r}") - return tuple(map(int, value.split("."))) - - -def policy(value): - if not isinstance(value, dict) or set(value) - {"requires_backend_min", "requires_backend_max"}: - raise ContractError("invalid compatibility metadata fields") - if "requires_backend_min" not in value: - raise ContractError("compatibility metadata requires requires_backend_min") - low = version(value["requires_backend_min"]) - high = version(value["requires_backend_max"]) if "requires_backend_max" in value else None - if high is not None and low > high: - raise ContractError("compatibility minimum exceeds maximum") - return low, high - - -def resolve(node, document, seen=()): - """Expand local refs; reject external refs and recursive schemas conservatively.""" - if isinstance(node, list): - return [resolve(item, document, seen) for item in node] - if not isinstance(node, dict): - return node - if "$ref" in node: - ref = node["$ref"] - if not ref.startswith("#/") or ref in seen: - raise ContractError(f"unsupported external or recursive reference: {ref}") - target = document - for key in ref[2:].split("/"): - target = target[key.replace("~1", "/").replace("~0", "~")] - target = copy.deepcopy(target) - target.update({key: value for key, value in node.items() if key != "$ref"}) - return resolve(target, document, (*seen, ref)) - return {key: resolve(value, document, seen) for key, value in node.items()} - - -def subset(source, target, location): - """Prove every source value is accepted by target, or fail closed. - - Requests: released consumer -> proposed producer. - Responses: proposed producer -> released consumer. - Unsupported changed constraints cannot accidentally establish compatibility. - """ - if source == target or target is True or target == {} or source is False: - return [] - if target is False or source is True: - return [f"{location}: schema acceptance changed"] - if not isinstance(source, dict) or not isinstance(target, dict): - return [f"{location}: unsupported schema change"] - ignored = {"title", "description", "examples", "example", "deprecated", "default", "readOnly", "writeOnly", "$schema", "x-rust-model"} - supported = {"type", "enum", "const", "properties", "required", "items", "additionalProperties", "minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems", "minProperties", "maxProperties"} - unknown = (set(source) | set(target)) - supported - ignored - errors = [] - for key in sorted(unknown): - if source.get(key) != target.get(key): - errors.append(f"{location}: changed {key} needs explicit compatibility evidence") - def types(schema): - value = schema.get("type", []) - return set(value if isinstance(value, list) else [value]) - a, b = types(source), types(target) - if b and (not a or any(t not in b and not (t == "integer" and "number" in b) for t in a)): - errors.append(f"{location}: type {sorted(a)} is not accepted by {sorted(b)}") - for key in ("enum", "const"): - if key in target: - candidates = source.get("enum", [source["const"]] if "const" in source else None) - accepted = target[key] if key == "enum" else [target[key]] - if candidates is None or any(item not in accepted for item in candidates): - errors.append(f"{location}: incompatible {key}") - for key in ("minimum", "minLength", "minItems", "minProperties"): - if key in target and (key not in source or source[key] < target[key]): - errors.append(f"{location}: tightened {key}") - for key in ("maximum", "maxLength", "maxItems", "maxProperties"): - if key in target and (key not in source or source[key] > target[key]): - errors.append(f"{location}: tightened {key}") - for key in sorted(set(target.get("required", [])) - set(source.get("required", []))): - errors.append(f"{location}.{key}: required property is not guaranteed") - source_props = source.get("properties", {}) - target_props = target.get("properties", {}) - source_extra = source.get("additionalProperties", True) - target_extra = target.get("additionalProperties", True) - for key in sorted(set(source_props) | set(target_props)): - left = source_props.get(key, source_extra) - right = target_props.get(key, target_extra) - if left is not False: - errors.extend(subset(left, right, f"{location}.{key}")) - errors.extend([] if source_extra is False else subset(source_extra, target_extra, f"{location}.*")) - if "items" in source or "items" in target: - errors.extend(subset(source.get("items", True), target.get("items", True), f"{location}[]")) - return errors - - -def operations(spec, variant): - result = {} - for path, item in spec["paths"].items(): - for method in METHODS & item.keys(): - op = item[method] - if variant in op.get("x-marreq-variants", ["server", "cloud"]): - result[(path, method)] = {**op, "parameters": item.get("parameters", []) + op.get("parameters", [])} - return result - - -def compare(consumer, backend, variant): - consumer_resolved = resolve(consumer, consumer) - backend_resolved = resolve(backend, backend) - validate_spec(consumer) - validate_spec(backend) - required = operations(consumer_resolved, variant) - offered = operations(backend_resolved, variant) - errors = [] - for key, old in required.items(): - path, method = key - at = f"{method.upper()} {path}" - if key not in offered: - errors.append(f"{at}: endpoint missing in {variant} backend") - continue - new = offered[key] - for op in (old, new): - if op.get("x-marreq-unverified"): - errors.append(f"{at}: contract has unverified behavior; compatibility cannot be established") - old_security = old.get("security", consumer.get("security", [])) - new_security = new.get("security", backend.get("security", [])) - if old_security != new_security: - errors.append(f"{at}: authentication/scopes changed") - if consumer.get("components", {}).get("securitySchemes") != backend.get("components", {}).get("securitySchemes"): - errors.append(f"{at}: security scheme definitions changed") - for name in ("x-marreq-policy", "x-marreq-csrf", "x-marreq-bearer-scopes"): - if old.get(name) != new.get(name): - errors.append(f"{at}: {name} changed") - old_params = {(p["in"], p["name"]): p for p in old.get("parameters", [])} - new_params = {(p["in"], p["name"]): p for p in new.get("parameters", [])} - for p, param in new_params.items(): - if p not in old_params: - if param.get("required"): - errors.append(f"{at}: new required {p} parameter") - else: - if param.get("required") and not old_params[p].get("required"): - errors.append(f"{at}: {p} parameter became required") - errors.extend(subset(old_params[p].get("schema", {}), param.get("schema", {}), f"{at} parameter {p}")) - if set(old_params) - set(new_params): - errors.append(f"{at}: accepted parameters removed") - old_body, new_body = old.get("requestBody", {}), new.get("requestBody", {}) - if new_body.get("required") and not old_body.get("required"): - errors.append(f"{at}: request body became required") - for media, content in old_body.get("content", {}).items(): - if media not in new_body.get("content", {}): - errors.append(f"{at}: request media {media} removed") - else: - errors.extend(subset(content.get("schema", {}), new_body["content"][media].get("schema", {}), f"{at} request")) - old_responses, new_responses = old["responses"], new["responses"] - # Even error status changes matter: clients branch on 401/403/404/409. - if old_responses.keys() != new_responses.keys(): - errors.append(f"{at}: response status codes changed") - for status in old_responses.keys() & new_responses.keys(): - previous = old_responses[status] - proposed = new_responses[status] - if previous.get("headers", {}) != proposed.get("headers", {}): - errors.append(f"{at} {status}: response headers changed") - old_content, new_content = previous.get("content", {}), proposed.get("content", {}) - if old_content.keys() != new_content.keys(): - errors.append(f"{at} {status}: response media changed") - for media in old_content.keys() & new_content.keys(): - errors.extend(subset(new_content[media].get("schema", {}), old_content[media].get("schema", {}), f"{at} response {status}")) - return sorted(set(errors)) - - -def digest(path): - return hashlib.sha256(Path(path).read_bytes()).hexdigest() - - -def read_bundle(descriptor, component, directory): - required = {"file", "sha256", "version", "source_sha"} - if not isinstance(descriptor, dict) or set(descriptor) != required: - raise ContractError(f"{component}: descriptor requires exactly {sorted(required)}") - version(descriptor["version"]) - if not re.fullmatch(r"[a-f0-9]{40}", descriptor["source_sha"]): - raise ContractError(f"{component}: immutable source SHA required") - relative = Path(descriptor["file"]) - if relative.is_absolute() or ".." in relative.parts: - raise ContractError(f"{component}: bundle file must stay inside the plan directory") - path = directory / relative - if not path.resolve().is_relative_to(directory.resolve()): - raise ContractError(f"{component}: bundle symlink escapes the plan directory") - if not path.is_file(): - raise ContractError(f"{component}: missing historical artifact {path}; never substitute current main") - if not re.fullmatch(r"[a-f0-9]{64}", descriptor["sha256"]) or digest(path) != descriptor["sha256"]: - raise ContractError(f"{component}: bundle SHA-256 mismatch") - bundle = load(path) - if bundle.get("format") != "marreq.contract.v1" or bundle.get("component") != component: - raise ContractError(f"{component}: wrong bundle format/component") - for key in ("version", "source_sha"): - if bundle.get(key) != descriptor[key]: - raise ContractError(f"{component}: bundle {key} disagrees with deployment plan") - resolve(bundle["contract"], bundle["contract"]) - validate_spec(bundle["contract"]) - return bundle - - -def verify_manifest(path): - manifest = load(path) - if not isinstance(manifest, dict) or set(manifest) != {"format", "variant", "backend", "frontend", "mcp"}: - raise ContractError("deployment plan requires format, variant, backend, frontend and mcp") - if manifest["format"] != "marreq.deployment-contract.v1" or manifest["variant"] not in ("server", "cloud"): - raise ContractError("invalid deployment plan format/variant") - bundles = {name: read_bundle(manifest[name], name, Path(path).parent) for name in ("backend", "frontend", "mcp")} - backend = bundles["backend"] - if backend.get("variant") != manifest["variant"]: - raise ContractError("backend artifact deployment variant mismatch") - errors = [] - backend_version = version(backend["version"]) - frontend_range = backend.get("frontend_compatibility", {}) - if set(frontend_range) != {"min_version", "max_version"}: - raise ContractError("backend frontend compatibility metadata is incomplete") - lo, hi = version(frontend_range["min_version"]), version(frontend_range["max_version"]) - if lo > hi: - raise ContractError("backend frontend compatibility range is inverted") - if not lo <= version(bundles["frontend"]["version"]) <= hi: - errors.append("frontend: outside backend's supported consumer range") - for name in ("frontend", "mcp"): - consumer = bundles[name] - low, high = policy(consumer.get("compatibility")) - if backend_version < low or (high is not None and backend_version > high): - errors.append(f"{name}: backend {backend['version']} is outside declared range") - errors.extend(f"{name}: {error}" for error in compare(consumer["contract"], backend["contract"], manifest["variant"])) - return {"compatible": not errors, "variant": manifest["variant"], "versions": {name: bundle["version"] for name, bundle in bundles.items()}, "errors": errors} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--manifest", required=True, type=Path) - parser.add_argument("--candidate-component", choices=["backend", "frontend", "mcp"]) - parser.add_argument("--candidate-version") - parser.add_argument("--candidate-source-sha") - parser.add_argument("--candidate-bundle", type=Path) - args = parser.parse_args() - try: - candidate = (args.candidate_component, args.candidate_version, args.candidate_source_sha) - if any(candidate): - if not all(candidate): - raise ContractError("candidate check requires component, version and source SHA") - manifest = load(args.manifest) - descriptor = manifest[args.candidate_component] - if descriptor["version"] != args.candidate_version or descriptor["source_sha"] != args.candidate_source_sha: - raise ContractError("deployment plan does not reference the exact tagged candidate; refusing to check a different artifact") - if args.candidate_bundle: - if not all(candidate): - raise ContractError("candidate bundle requires candidate identity") - if digest(args.candidate_bundle) != descriptor["sha256"]: - raise ContractError("candidate contract is not the source-exported bundle for this tag") - result = verify_manifest(args.manifest) - except (ValueError, OSError, KeyError, TypeError) as error: - result = {"compatible": False, "errors": [str(error)]} - except Exception as error: # validators also have their own exception types - result = {"compatible": False, "errors": [f"invalid contract: {error}"]} - print(json.dumps(result, indent=2)) - return 0 if result["compatible"] else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/contracts/generate.py b/scripts/contracts/generate.py deleted file mode 100644 index b9037409..00000000 --- a/scripts/contracts/generate.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -"""Reproduce TypeScript wire types and compiled-Rocket route expectations.""" -import argparse -import json -import sys - -from compatibility import ROOT, load - - -def ts(node): - if node is True or node == {}: - return "unknown" - if node is False: - return "never" - if "$ref" in node: - return node["$ref"].split("/")[-1] - for key in ("oneOf", "anyOf", "allOf"): - if key in node: - join = " & " if key == "allOf" else " | " - return "(" + join.join(ts(item) for item in node[key]) + ")" - if "const" in node: - return json.dumps(node["const"]) - if "enum" in node: - return " | ".join(json.dumps(item) for item in node["enum"]) - kind = node.get("type") - if isinstance(kind, list): - return " | ".join(ts({**node, "type": value}) for value in kind) - if kind == "null": - return "null" - if kind in ("number", "integer"): - return "number" - if kind in ("string", "boolean"): - return kind - if kind == "array": - return f"Array<{ts(node.get('items', {}))}>" - if kind == "object" or "properties" in node: - lines = [] - for name, value in node.get("properties", {}).items(): - optional = "" if name in node.get("required", []) else "?" - lines.append(f" {json.dumps(name)}{optional}: {ts(value)};") - extra = node.get("additionalProperties", True) - if isinstance(extra, dict): - lines.append(f" [key: string]: {ts(extra)};") - elif extra: - lines.append(" [key: string]: unknown;") - return "{\n" + "\n".join(lines) + "\n}" - if node.get("x-marreq-unverified"): - return "unknown" - raise ValueError(f"unsupported TypeScript schema: {node}") - - -def generated(spec): - header = "// Generated by scripts/contracts/generate.py from docs/developer/openapi.yaml.\n// Do not edit; this file carries wire types, not a second contract.\n\n" - types = header + "\n\n".join(f"export type {name} = {ts(schema)};" for name, schema in sorted(spec["components"]["schemas"].items())) + "\n" - routes = [] - for path, item in spec["paths"].items(): - for method, operation in item.items(): - routes.append({"method": method.upper(), "path": path, "variants": operation.get("x-marreq-variants", ["server", "cloud"])}) - return { - ROOT / "frontend/src/api/generated-contract.ts": types, - ROOT / "mcp-server/src/generated-contract.ts": types, - ROOT / "docs/developer/api-contract-routes.json": json.dumps(routes, indent=2) + "\n", - } - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--check", action="store_true") - args = parser.parse_args() - stale = [] - for path, content in generated(load(ROOT / "docs/developer/openapi.yaml")).items(): - if args.check: - if not path.exists() or path.read_text() != content: - stale.append(str(path.relative_to(ROOT))) - else: - path.write_text(content) - if stale: - print("Generated contract drift; run python3 scripts/contracts/generate.py:\n" + "\n".join(stale), file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/contracts/inventory.py b/scripts/contracts/inventory.py deleted file mode 100644 index 9376ed63..00000000 --- a/scripts/contracts/inventory.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Source audit helpers, corroborated by Rocket's compiled route inventory. - -This extracts route signatures, not Rust syntax or inferred serialization schemas. -The checked-in OpenAPI remains the authoritative wire contract. -""" -from pathlib import Path -import re - -ROOT = Path(__file__).resolve().parents[2] - - -def source_routes(root=ROOT): - routes = [] - for directory, variant in (("marreq-core/src/api", "shared"), ("marreq-cloud/src/api", "cloud"), ("marreq-server/src/api", "server")): - for file in sorted((root / directory).glob("*.rs")): - text = file.read_text() - # Rocket route attributes always precede a free handler function. - pattern = r'#\[(get|post|put|patch|delete)\(\s*"([^"]+)"(.*?)\)\]\s*pub (?:async )?fn (\w+)\s*\((.*?)\)\s*(?:->\s*(.*?))?\s*\{' - for match in re.finditer(pattern, text, re.S): - method, uri, attributes, name, arguments, returns = match.groups() - base = "" if file.stem == "oauth" else "/api" - path, _, query = uri.partition("?") - path = base + re.sub(r"<([\w]+)(?:\.\.)?>", r"{\1}", path) - guards = re.findall(r"\b(?:ApiUser(?:OrBearer)?|SessionUser|OptionalSessionUser|AdminOnly|Project[A-Z][A-Za-z]*|ProjectsRead|StdioMcpAuditAuth|McpAuditAuth|McpPrincipalAuth|InternalMcpAudit)\b", arguments) - routes.append({"path": path, "method": method, "handler": f"{file.stem}::{name}", "source": str(file.relative_to(root)), "variant": variant, "arguments": arguments.strip(), "returns": (returns or "()").strip(), "query": query, "guards": guards, "data": re.findall(r'data\s*=\s*"<([^>]+)>"', attributes)}) - return routes diff --git a/scripts/contracts/requirements.txt b/scripts/contracts/requirements.txt deleted file mode 100644 index d21b1402..00000000 --- a/scripts/contracts/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -PyYAML==6.0.3 -jsonschema==4.26.0 -openapi-spec-validator==0.7.2 diff --git a/scripts/contracts/test_compatibility.py b/scripts/contracts/test_compatibility.py deleted file mode 100644 index 823c9d34..00000000 --- a/scripts/contracts/test_compatibility.py +++ /dev/null @@ -1,227 +0,0 @@ -import copy -import hashlib -import json -from pathlib import Path -import subprocess -import tempfile -import unittest - -from compatibility import ContractError, ROOT, compare, policy, subset, verify_manifest - - -def spec(): - obj = {"type": "object", "properties": {"id": {"type": "integer"}, "state": {"type": "string", "enum": ["draft", "approved"]}}, "required": ["id", "state"], "additionalProperties": True} - return {"openapi": "3.1.0", "info": {"title": "fixture", "version": "1.0.0"}, "paths": {"/api/things/{id}": {"post": {"security": [{"bearerAuth": []}], "parameters": [{"in": "path", "name": "id", "required": True, "schema": {"type": "integer"}}], "requestBody": {"required": True, "content": {"application/json": {"schema": {"type": "object", "required": ["title"], "properties": {"title": {"type": "string"}}, "additionalProperties": False}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": obj}}}, "401": {"description": "Unauthorized"}}}}}, "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer"}}}} - - -def operation(document): - return document["paths"]["/api/things/{id}"]["post"] - - -def response(document): - return operation(document)["responses"]["200"]["content"]["application/json"]["schema"] - - -def request(document): - return operation(document)["requestBody"]["content"]["application/json"]["schema"] - - -class CompatibilityTests(unittest.TestCase): - def setUp(self): - self.old = spec() - self.new = copy.deepcopy(self.old) - - def check(self): - return compare(self.old, self.new, "server") - - def test_valid_openapi(self): - self.assertEqual(self.check(), []) - - def test_removed_route(self): - self.new["paths"] = {} - self.assertIn("endpoint missing", self.check()[0]) - - def test_incompatible_request_type(self): - request(self.new)["properties"]["title"] = {"type": "integer"} - self.assertTrue(self.check()) - - def test_incompatible_response_type(self): - response(self.new)["properties"]["id"] = {"type": "string"} - self.assertTrue(self.check()) - - def test_removed_required_response(self): - response(self.new)["required"].remove("id") - self.assertTrue(self.check()) - - def test_new_required_request(self): - request(self.new)["properties"]["reason"] = {"type": "string"} - request(self.new)["required"].append("reason") - self.assertTrue(self.check()) - - def test_optional_request_addition(self): - request(self.new)["properties"]["reason"] = {"type": "string"} - self.assertEqual(self.check(), []) - - def test_response_addition(self): - response(self.new)["properties"]["extra"] = {"type": "boolean"} - response(self.new)["required"].append("extra") - self.assertEqual(self.check(), []) - - def test_response_enum_extension_breaks_exhaustive_consumers(self): - response(self.new)["properties"]["state"]["enum"].append("reviewed") - self.assertTrue(self.check()) - - def test_request_enum_restriction(self): - request(self.old)["properties"]["title"]["enum"] = ["a", "b"] - request(self.new)["properties"]["title"]["enum"] = ["a"] - self.assertTrue(self.check()) - - def test_oauth_scope_mismatch(self): - operation(self.new)["x-marreq-bearer-scopes"] = ["things:write"] - self.assertTrue(self.check()) - - def test_authentication_mismatch(self): - operation(self.new)["security"] = [] - self.assertTrue(self.check()) - - def test_status_code_mismatch(self): - operation(self.new)["responses"]["201"] = operation(self.new)["responses"].pop("200") - self.assertTrue(self.check()) - - def test_variants(self): - operation(self.old)["x-marreq-variants"] = ["cloud"] - self.new["paths"] = {} - self.assertEqual(self.check(), []) - self.assertTrue(compare(self.old, self.new, "cloud")) - - def test_unverified_contract_fails_even_when_identical(self): - operation(self.old)["x-marreq-unverified"] = True - self.assertTrue(compare(self.old, self.old, "cloud")) - - def test_unsupported_constraint_changes_fail_closed(self): - self.assertTrue(subset({"type": "string"}, {"type": "string", "pattern": "a+"}, "request")) - - def test_nullable_response_breaks_nonnullable_consumer(self): - self.assertTrue(subset({"type": ["string", "null"]}, {"type": "string"}, "response")) - - def test_metadata_invalid_or_incomplete(self): - for value in ({}, None, {"requires_backend_min": "1.x"}, {"requires_backend_min": "2.0.0", "requires_backend_max": "1.0.0"}, {"requires_backend_min": "1.0.0", "typo": "x"}): - with self.subTest(value=value), self.assertRaises(ContractError): - policy(value) - - def deployment(self, directory, incompatible=False, backend_version="1.1.0"): - plan = {"format": "marreq.deployment-contract.v1", "variant": "server"} - for component, ver in (("backend", backend_version), ("frontend", "1.0.0"), ("mcp", "1.2.0")): - contract = spec() - if incompatible and component == "backend": - response(contract)["required"].remove("id") - bundle = {"format": "marreq.contract.v1", "component": component, "version": ver, "source_sha": "a" * 40, "contract": contract} - if component == "backend": - bundle.update(variant="server", frontend_compatibility={"min_version": "1.0.0", "max_version": "2.0.0"}) - else: - bundle["compatibility"] = {"requires_backend_min": "1.0.0", "requires_backend_max": "1.9.0"} - data = json.dumps(bundle).encode() - (directory / f"{component}.json").write_bytes(data) - plan[component] = {"file": f"{component}.json", "sha256": hashlib.sha256(data).hexdigest(), "version": ver, "source_sha": "a" * 40} - path = directory / "plan.json" - path.write_text(json.dumps(plan)) - return path - - def test_unchanged_frontend_and_mcp_with_new_compatible_backend(self): - with tempfile.TemporaryDirectory() as temp: - result = verify_manifest(self.deployment(Path(temp))) - self.assertTrue(result["compatible"]) - self.assertNotEqual(result["versions"]["frontend"], result["versions"]["backend"]) - self.assertNotEqual(result["versions"]["mcp"], result["versions"]["backend"]) - - def test_unchanged_consumers_with_incompatible_backend(self): - with tempfile.TemporaryDirectory() as temp: - result = verify_manifest(self.deployment(Path(temp), incompatible=True)) - self.assertFalse(result["compatible"]) - self.assertTrue(any(e.startswith("frontend:") for e in result["errors"])) - self.assertTrue(any(e.startswith("mcp:") for e in result["errors"])) - - def test_versions_fail_independently_of_contracts(self): - with tempfile.TemporaryDirectory() as temp: - result = verify_manifest(self.deployment(Path(temp), backend_version="2.0.0")) - self.assertFalse(result["compatible"]) - - def test_missing_historical_artifact(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - (Path(temp) / "mcp.json").unlink() - with self.assertRaisesRegex(ContractError, "missing historical artifact"): - verify_manifest(path) - - def test_tampered_artifact(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - with (Path(temp) / "frontend.json").open("a") as stream: - stream.write(" ") - with self.assertRaisesRegex(ContractError, "SHA-256 mismatch"): - verify_manifest(path) - - def test_descriptor_version_cannot_substitute_another_artifact(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - plan = json.loads(path.read_text()) - plan["frontend"]["version"] = "1.0.1" - path.write_text(json.dumps(plan)) - with self.assertRaisesRegex(ContractError, "version disagrees"): - verify_manifest(path) - - def test_bundle_path_cannot_escape_plan_directory(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - plan = json.loads(path.read_text()) - plan["mcp"]["file"] = "../mcp.json" - path.write_text(json.dumps(plan)) - with self.assertRaisesRegex(ContractError, "inside the plan directory"): - verify_manifest(path) - - def test_backend_variant_cannot_be_substituted(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - plan = json.loads(path.read_text()) - plan["variant"] = "cloud" - path.write_text(json.dumps(plan)) - with self.assertRaisesRegex(ContractError, "variant mismatch"): - verify_manifest(path) - - def test_candidate_plan_must_reference_tagged_source(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - import sys - result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path), "--candidate-component", "backend", "--candidate-version", "1.1.0", "--candidate-source-sha", "b" * 40], capture_output=True, text=True) - self.assertEqual(result.returncode, 1) - self.assertIn("exact tagged candidate", json.loads(result.stdout)["errors"][0]) - - def test_invalid_openapi_does_not_pass_compatibility(self): - self.new.pop("info") - with self.assertRaises(Exception): - self.check() - - def test_external_refs_are_rejected_before_any_validator_network_access(self): - response(self.new)["properties"]["id"] = {"$ref": "https://example.test/schema.json"} - with self.assertRaisesRegex(ContractError, "external or recursive"): - self.check() - - def test_release_gate_zero_for_compatible_independent_artifacts(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp)) - import sys - result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path)], capture_output=True, text=True) - self.assertEqual(result.returncode, 0) - self.assertTrue(json.loads(result.stdout)["compatible"]) - - def test_release_gate_nonzero_on_real_violation(self): - with tempfile.TemporaryDirectory() as temp: - path = self.deployment(Path(temp), incompatible=True) - import sys - result = subprocess.run([sys.executable, str(ROOT / "scripts/contracts/compatibility.py"), "--manifest", str(path)], capture_output=True, text=True) - self.assertEqual(result.returncode, 1) - self.assertFalse(json.loads(result.stdout)["compatible"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/contracts/test_contract_tooling.py b/scripts/contracts/test_contract_tooling.py deleted file mode 100644 index 78ef3d3f..00000000 --- a/scripts/contracts/test_contract_tooling.py +++ /dev/null @@ -1,70 +0,0 @@ -import copy -import json -from pathlib import Path -import tempfile -import unittest -from unittest.mock import patch - -from compatibility import ContractError, ROOT, load -from check import check -from check_baseline import check as check_baseline -from generate import generated -from test_compatibility import spec, response -from verify_transcript import verify - - -class ProducerTests(unittest.TestCase): - def test_openapi_has_all_source_routes_and_generated_artifacts(self): - # Review lock is checked separately and cannot stand in for route/schema checks. - errors, gaps = check() - self.assertEqual(errors, []) - self.assertTrue(gaps) # Be honest about the bootstrap's incomplete evidence. - - def test_documented_route_missing_from_actual_source_is_rejected(self): - from inventory import source_routes - routes = source_routes() - with patch("check.source_routes", return_value=routes[1:]): - errors, _ = check() - self.assertTrue(any("documented route missing in backend" in error for error in errors)) - - def test_cloud_route_accidentally_enabled_on_server_is_rejected(self): - from inventory import source_routes - routes = copy.deepcopy(source_routes()) - next(route for route in routes if route["variant"] == "cloud")["variant"] = "shared" - with patch("check.source_routes", return_value=routes): - errors, _ = check() - self.assertTrue(any("deployment variant mismatch" in error for error in errors)) - - def test_source_change_without_contract_review_is_rejected(self): - with patch("check.source_evidence", return_value={"changed.rs": "f" * 64}): - errors, _ = check() - self.assertTrue(any("API source changed" in error for error in errors)) - - def test_release_completeness_cannot_pass_inventory_only_entries(self): - errors, gaps = check(require_complete=True) - self.assertTrue(gaps) - self.assertTrue(any("unverified wire contract" in error for error in errors)) - - def test_audited_baseline_rejects_required_response_removal(self): - old, new = spec(), spec() - old["x-marreq-contract-complete"] = False - response(new)["required"].remove("id") - self.assertTrue(check_baseline(old, new)["errors"]) - - def test_generated_types_come_from_the_contract(self): - document = load(ROOT / "docs/developer/openapi.yaml") - result = generated(document) - document["components"]["schemas"]["BuildInfo"]["properties"]["new_required"] = {"type": "integer"} - document["components"]["schemas"]["BuildInfo"]["required"].append("new_required") - self.assertNotEqual(result, generated(document)) - - def test_empty_integration_transcript_is_not_a_passing_backend_test(self): - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "exchanges.jsonl" - path.write_text("") - with self.assertRaisesRegex(ContractError, "lacks critical cases"): - verify(path) - - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/contracts/verify_transcript.py b/scripts/contracts/verify_transcript.py deleted file mode 100644 index c513cb5b..00000000 --- a/scripts/contracts/verify_transcript.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -"""Validate real Rocket responses and requests recorded by the DB integration test. - -No credentials are recorded. Inventory-only operations are counted separately. -""" -import argparse -import copy -import tempfile -from pathlib import Path -import json -import re -import sys - -from jsonschema import Draft202012Validator, ValidationError -from compatibility import ROOT, ContractError, load, operations, resolve - - -def verify(path): - spec = load(ROOT / "docs/developer/openapi.yaml") - declared = operations(resolve(spec, spec), "server") - records = [json.loads(line) for line in path.read_text().splitlines() if line.strip()] - checked, inventory_only, seen = 0, 0, set() - for record in records: - matches = [(p, op) for (p, m), op in declared.items() if m.upper() == record["method"] and re.fullmatch(re.sub(r"\{[^}]+\}", "[^/]+", re.escape(p).replace(r"\{", "{").replace(r"\}", "}")), record["path"].split("?")[0])] - if matches: - specificity = min(p.count("{") for p, _ in matches) - matches = [(p, op) for p, op in matches if p.count("{") == specificity] - if len(matches) != 1: - raise ContractError(f"ambiguous or missing operation for {record['method']} {record['path']}") - path_template, op = matches[0] - if op.get("x-marreq-unverified"): - inventory_only += 1 - continue - status = str(record["status"]) - response = op["responses"].get(status, op["responses"].get("default")) - if response is None: - raise ContractError(f"{path_template}: undocumented actual status {status}") - body = record["response"] - schema = response.get("content", {}).get("application/json", {}).get("schema") - if schema is not None: - Draft202012Validator(schema).validate(body) - if record.get("request") is not None and status.startswith("2"): - request_schema = op.get("requestBody", {}).get("content", {}).get("application/json", {}).get("schema") - if request_schema is None: - raise ContractError(f"{path_template}: undocumented JSON request") - Draft202012Validator(request_schema).validate(record["request"]) - checked += 1 - seen.add((record["method"], path_template, status)) - required = {("GET", "/api/meta/build", "200"), ("GET", "/api/meta/deployment", "200"), ("GET", "/api/projects/{project_id}/use-cases", "200"), ("POST", "/api/projects/{project_id}/use-cases", "201"), ("PUT", "/api/projects/{project_id}/use-cases/{id}/draft", "200"), ("GET", "/api/projects/{project_id}/use-cases", "403")} - missing = required - seen - if missing: - raise ContractError(f"integration transcript lacks critical cases: {sorted(missing)}") - return {"validated_exchanges": checked, "inventory_only_exchanges": inventory_only} - - -def negative_controls(path): - """Demonstrate rejection using corrupted copies of actual backend traffic.""" - records = [json.loads(line) for line in path.read_text().splitlines() if line.strip()] - cases = [] - for label in ("removed guaranteed response property", "incompatible response type", "incompatible MCP request"): - mutated = copy.deepcopy(records) - if label == "incompatible MCP request": - entry = next(r for r in mutated if r["method"] == "POST" and r["status"] == 201 and r["path"].endswith("/use-cases")) - entry["request"]["title"] = 123 - else: - entry = next(r for r in mutated if r["path"] == "/api/meta/build") - if label == "removed guaranteed response property": - del entry["response"]["backend_version"] - else: - entry["response"]["backend_version"] = 123 - with tempfile.TemporaryDirectory() as directory: - candidate = Path(directory) / "negative.jsonl" - candidate.write_text("\n".join(json.dumps(r) for r in mutated) + "\n") - try: - verify(candidate) - except ValidationError: - cases.append(label) - else: - raise ContractError(f"negative control was silently accepted: {label}") - return cases - - -if __name__ == "__main__": - from pathlib import Path - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("transcript", type=Path) - args = parser.parse_args() - result = verify(args.transcript) - result["rejected_negative_controls"] = negative_controls(args.transcript) - print(json.dumps(result, indent=2)) diff --git a/scripts/generate-api-types.mjs b/scripts/generate-api-types.mjs new file mode 100644 index 00000000..2f3144a1 --- /dev/null +++ b/scripts/generate-api-types.mjs @@ -0,0 +1,26 @@ +// Standard OpenAPI -> TypeScript generation; only component-reachable schemas. +// Committed declarations keep Rust/global OpenAPI out of consumer Docker builds. +import { execFileSync } from 'node:child_process'; +import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import openapiTS, { astToString } from 'openapi-typescript'; +const check = process.argv.includes('--check'); +const directory = mkdtempSync(join(tmpdir(), 'marreq-types-')); +try { + for (const component of ['frontend', 'mcp']) { + const path = join(directory, `${component}.json`); + // Inventory-only entries generate no invented types. Strict release/CI + // coverage is checked separately by `marreq-contracts validate --consumers`. + execFileSync('cargo', ['run', '--quiet', '--locked', '-p', 'marreq-contracts', '--', 'select', '--component', component, '--allow-unverified', '--output', path]); + const spec = JSON.parse(readFileSync(path, 'utf8')); + spec.paths = {}; // Clients currently import schemas, not generated SDK paths. + const ast = await openapiTS(spec); + const declarations = ast.filter(node => node.name?.escapedText === 'components'); + const result = '// Generated by scripts/generate-api-types.mjs; do not edit.\n' + astToString(declarations); + const output = component === 'frontend' ? 'frontend/src/api/generated-contract.ts' : 'mcp-server/src/generated-contract.ts'; + if (check) { + if (readFileSync(output, 'utf8') !== result) throw new Error(`${output}: generated type drift; run node scripts/generate-api-types.mjs`); + } else writeFileSync(output, result); + } +} finally { rmSync(directory, { recursive: true, force: true }); } diff --git a/scripts/install-oasdiff.sh b/scripts/install-oasdiff.sh new file mode 100755 index 00000000..9cd25409 --- /dev/null +++ b/scripts/install-oasdiff.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Pinned upstream standards engine, used only by developer/CI Rust tooling. +set -euo pipefail +[[ $(uname -s) == Linux && $(uname -m) == x86_64 ]] || { echo 'Install oasdiff 1.34.0 for your platform and set OASDIFF.' >&2; exit 1; } +destination=${1:-"$HOME/.cache/marreq-contracts"} +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 \ + https://github.com/oasdiff/oasdiff/releases/download/v1.34.0/oasdiff_1.34.0_linux_amd64.tar.gz \ + --output "$work/oasdiff.tar.gz" +printf '%s %s\n' 7c88e09fd500b231af2819eb21f6fdfc7bcdfe2741d2c1937c59e7b72c8a575a "$work/oasdiff.tar.gz" | sha256sum --check +mkdir -p "$destination" +tar -xzf "$work/oasdiff.tar.gz" -C "$work" oasdiff +install -m 755 "$work/oasdiff" "$destination/oasdiff" +if [[ -n ${GITHUB_PATH:-} ]]; then printf '%s\n' "$destination" >> "$GITHUB_PATH"; fi diff --git a/scripts/contracts/smoke-consumers.mjs b/scripts/smoke-api-consumers.mjs similarity index 51% rename from scripts/contracts/smoke-consumers.mjs rename to scripts/smoke-api-consumers.mjs index abbb0821..8de0086d 100644 --- a/scripts/contracts/smoke-consumers.mjs +++ b/scripts/smoke-api-consumers.mjs @@ -1,26 +1,37 @@ -// Real HTTP producer/consumer smoke test. Run only through test-backend.sh: +// Real HTTP producer/consumer smoke test. Run only through test-api-contracts.sh: // it provides an isolated fixture DB and loopback-only Rocket process. import assert from 'node:assert/strict'; import { appendFileSync } from 'node:fs'; -import { MarreqClient } from '../../mcp-server/dist/client.js'; -import { fetchJson } from '../../frontend/src/api/transport.ts'; +import { MarreqClient } from '../mcp-server/dist/client.js'; +import { fetchJson } from '../frontend/src/api/transport.ts'; +import { getCsrfToken, loginJson, getMe, logoutJson } from '../frontend/src/api/auth.ts'; +import { requestPasswordReset } from '../frontend/src/api/publicAuth.ts'; const base = process.env.MARREQ_CONTRACT_BACKEND_URL; assert.match(base ?? '', /^http:\/\/127\.0\.0\.1:\d+$/); assert.ok(process.env.MARREQ_CONTRACT_TRANSCRIPT); +const mode = process.env.MARREQ_CONTRACT_VARIANT ?? 'server'; +const cookies = new Map(); const networkFetch = globalThis.fetch; globalThis.fetch = async (input, init = {}) => { const url = new URL(input, base); assert.equal(url.origin, base, 'contract smoke must stay on the isolated backend'); - const response = await networkFetch(url, init); + const headers = new Headers(init.headers); + headers.set('Origin', base); + if (cookies.size) headers.set('Cookie', [...cookies].map(([k,v]) => `${k}=${v}`).join('; ')); + const response = await networkFetch(url, {...init, headers}); + for (const value of response.headers.getSetCookie()) { + const cookie = value.split(';')[0]; const equal = cookie.indexOf('='); + cookies.set(cookie.slice(0,equal), cookie.slice(equal+1)); + } const text = await response.clone().text(); let body = null; try { body = JSON.parse(text); } catch { /* Empty guard and 204 bodies are valid. */ } // Authorization headers/cookies are never retained in the transcript. appendFileSync(process.env.MARREQ_CONTRACT_TRANSCRIPT, JSON.stringify({ - method: init.method ?? 'GET', path: url.pathname + url.search, + variant: mode, method: init.method ?? 'GET', path: url.pathname + url.search, status: response.status, - request: typeof init.body === 'string' ? JSON.parse(init.body) : null, + request: url.pathname === '/api/auth/login' ? {username:'[test-user]',password:'[redacted]'} : typeof init.body === 'string' ? JSON.parse(init.body) : null, response: body, }) + '\n'); return response; @@ -28,22 +39,34 @@ globalThis.fetch = async (input, init = {}) => { // Execute the actual frontend transport against the running Rocket binary. const build = await fetchJson('/api/meta/build'); -assert.equal(build.deployment_mode, 'server'); +assert.equal(build.deployment_mode, mode); const capabilities = await fetchJson('/api/meta/deployment'); -assert.equal(capabilities.allows_self_registration, false); +assert.equal(capabilities.allows_self_registration, mode === 'cloud'); +assert.equal(capabilities.allows_database_backup, mode === 'server'); +assert.equal(capabilities.allows_self_administered_user_creation, mode === 'server'); +const discovery = await fetchJson('/.well-known/oauth-authorization-server'); +assert.ok(discovery.scopes_supported.includes('use_cases:write')); +assert.deepEqual(discovery.token_endpoint_auth_methods_supported, ['none']); +const csrf = await getCsrfToken(); +await loginJson('manager', 'Contract-only-fixture-58!z', csrf); +assert.equal((await getMe()).username, 'manager'); +await logoutJson(await getCsrfToken()); +await assert.rejects(() => getMe(), (error) => error.status === 401); +if (mode === 'cloud') await requestPasswordReset({email:'absent-contract-user@example.test'}); // These delegated fixture credentials exist only in the ephemeral DB. const context = { baseUrl: base, apiToken: 'uc-write', projectId: 1, mode: 'draft_write', traceWrite: false }; const writer = new MarreqClient(context); const reader = new MarreqClient({ ...context, apiToken: 'uc-read' }); +assert.equal((await reader.getPrincipal()).authentication_type, 'delegated_oauth'); assert.ok(Array.isArray(await reader.listUseCases())); -const created = await writer.createUseCase({ title: 'Network contract use case', primary_actor: 'Tester' }, 'network-contract-create-key'); +const created = await writer.createUseCase({ title: 'Network contract use case', primary_actor: 'Tester' }, `network-contract-create-key-${mode}`); assert.equal(typeof created.id, 'number'); const document = { schema: 'marreq.use-case.v1', title: 'Network contract use case', main: [{ id: 's_net01', text: 'Exercise the real client against Rocket' }] }; const saved = await writer.putUseCaseDraft(created.id, created.draft.revision, document); assert.equal(saved.document.main[0].id, 's_net01'); const detail = await reader.getUseCase(created.id); assert.equal(detail.id, created.id); -await assert.rejects(() => reader.createUseCase({ title: 'Denied write' }, 'network-contract-denied-key'), (error) => error.status === 403); +await assert.rejects(() => reader.createUseCase({ title: 'Denied write' }, `network-contract-denied-key-${mode}`), (error) => error.status === 403 && error.challenge?.includes('use_cases:write')); await assert.rejects(() => reader.withProject(2).listUseCases(), (error) => error.status === 403 || /403/.test(error.message)); console.log('Real frontend transport and MCP HTTP read/write/scope/project-boundary checks passed.'); diff --git a/scripts/contracts/test-backend.sh b/scripts/test-api-contracts.sh similarity index 54% rename from scripts/contracts/test-backend.sh rename to scripts/test-api-contracts.sh index 4a1c66d4..64866abe 100755 --- a/scripts/contracts/test-backend.sh +++ b/scripts/test-api-contracts.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Creates a disposable DB; ignores DATABASE_URL and .env. No production access. set -euo pipefail -cd "$(dirname "$0")/../.." +cd "$(dirname "$0")/.." work=$(mktemp -d) container="marreq-contract-$RANDOM-$$" backend_pid= @@ -28,7 +28,7 @@ export MARREQ_TEST_DATABASE_URL="postgres://contract_test:disposable-contract-te export DATABASE_URL="$MARREQ_TEST_DATABASE_URL" export MARREQ_CONTRACT_TRANSCRIPT="$work/exchanges.jsonl" cargo test --locked -p marreq-core --no-default-features --test delegated_oauth_mcp_tools_postgres_integration --no-run --message-format=json > "$work/test-build.jsonl" -test_executable=$(python3 -c 'import json,sys; print(next(r["executable"] for r in map(json.loads,open(sys.argv[1])) if r.get("executable") and r["target"]["name"]=="delegated_oauth_mcp_tools_postgres_integration"))' "$work/test-build.jsonl") +test_executable=$(node -e 'const fs=require("node:fs"); console.log(fs.readFileSync(process.argv[1],"utf8").trim().split("\n").map(JSON.parse).find(r=>r.executable&&r.target.name==="delegated_oauth_mcp_tools_postgres_integration").executable)' "$work/test-build.jsonl") # A clean working directory and environment prevent dotenv/SMTP/semantic-search # configuration inherited from a developer machine from entering test processes. (cd "$work" && env -i PATH="$PATH" LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ @@ -38,29 +38,33 @@ test_executable=$(python3 -c 'import json,sys; print(next(r["executable"] for r # Run the actual frontend transport and MCP HTTP client against a live socket. # All database and service configuration is pinned to this ephemeral test stack. npm run build --prefix mcp-server -cargo build --locked -p marreq-server --message-format=json > "$work/server-build.jsonl" -server_executable=$(python3 -c 'import json,sys; print(next(r["executable"] for r in map(json.loads,open(sys.argv[1])) if r.get("executable") and r["target"]["name"]=="marreq-server"))' "$work/server-build.jsonl") -backend_port=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()') -export ROCKET_ADDRESS=127.0.0.1 ROCKET_PORT="$backend_port" +cargo build --locked -p marreq-server -p marreq-cloud --message-format=json > "$work/server-build.jsonl" +frontend/node_modules/.bin/rolldown scripts/smoke-api-consumers.mjs --platform node --format esm --file "$work/smoke.mjs" export ROCKET_DATABASES="{my_db={url=\"$DATABASE_URL\",pool_size=2}}" export MARREQ_PUBLIC_BASE_URL=http://localhost:8080 MARREQ_MCP_PUBLIC_URL=http://localhost:8080/mcp export MARREQ_MCP_AUDIT_SECRET=integration-test-audit-secret-32-bytes -export MARREQ_AUTO_SEED=false MARREQ_UI_MODE=api_only -export MARREQ_CONTRACT_BACKEND_URL="http://127.0.0.1:$backend_port" -(cd "$work" && exec env -i PATH="$PATH" LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ - DATABASE_URL="$DATABASE_URL" ROCKET_ADDRESS=127.0.0.1 ROCKET_PORT="$backend_port" \ - ROCKET_DATABASES="$ROCKET_DATABASES" MARREQ_PUBLIC_BASE_URL="$MARREQ_PUBLIC_BASE_URL" \ - MARREQ_MCP_PUBLIC_URL="$MARREQ_MCP_PUBLIC_URL" MARREQ_MCP_AUDIT_SECRET="$MARREQ_MCP_AUDIT_SECRET" \ - MARREQ_AUTO_SEED=false MARREQ_UI_MODE=api_only "$server_executable") > "$work/backend.log" 2>&1 & -backend_pid=$! -ready=false -for attempt in {1..60}; do - if ! kill -0 "$backend_pid" 2>/dev/null; then cat "$work/backend.log"; exit 1; fi - if curl --silent --fail "$MARREQ_CONTRACT_BACKEND_URL/api/meta/health" > /dev/null; then ready=true; break; fi - sleep 1 +for mode in server cloud; do + executable=$(node -e 'const fs=require("node:fs"); console.log(fs.readFileSync(process.argv[1],"utf8").trim().split("\n").map(JSON.parse).find(r=>r.executable&&r.target.name===process.argv[2]).executable)' "$work/server-build.jsonl" "marreq-$mode") + backend_port=$(node -e 'const s=require("node:net").createServer();s.listen(0,"127.0.0.1",()=>{console.log(s.address().port);s.close()})') + export MARREQ_CONTRACT_BACKEND_URL="http://127.0.0.1:$backend_port" MARREQ_CONTRACT_VARIANT="$mode" + (cd "$work" && exec env -i PATH="$PATH" LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}" \ + DATABASE_URL="$DATABASE_URL" ROCKET_ADDRESS=127.0.0.1 ROCKET_PORT="$backend_port" \ + ROCKET_DATABASES="$ROCKET_DATABASES" MARREQ_PUBLIC_BASE_URL="$MARREQ_PUBLIC_BASE_URL" \ + MARREQ_MCP_PUBLIC_URL="$MARREQ_MCP_PUBLIC_URL" MARREQ_MCP_AUDIT_SECRET="$MARREQ_MCP_AUDIT_SECRET" \ + CSRF_ALLOWED_ORIGINS="$MARREQ_CONTRACT_BACKEND_URL" \ + MARREQ_AUTO_SEED=false MARREQ_UI_MODE=api_only "$executable") > "$work/backend.log" 2>&1 & + backend_pid=$! + ready=false + for attempt in {1..60}; do + if ! kill -0 "$backend_pid" 2>/dev/null; then cat "$work/backend.log"; exit 1; fi + if curl --silent --fail "$MARREQ_CONTRACT_BACKEND_URL/api/meta/health" > /dev/null; then ready=true; break; fi + sleep 1 + done + if [[ "$ready" != true ]]; then cat "$work/backend.log"; exit 1; fi + node "$work/smoke.mjs" + kill "$backend_pid" + wait "$backend_pid" || true + backend_pid= done -if [[ "$ready" != true ]]; then cat "$work/backend.log"; exit 1; fi -frontend/node_modules/.bin/rolldown scripts/contracts/smoke-consumers.mjs --platform node --format esm --file "$work/smoke.mjs" -node "$work/smoke.mjs" -# Fails if the test skipped, produced no transcript, or omitted a critical case. -python3 scripts/contracts/verify_transcript.py "$MARREQ_CONTRACT_TRANSCRIPT" +# Missing/skipped critical exchanges fail, not just malformed response bodies. +cargo run --quiet --locked -p marreq-contracts -- verify-traffic "$MARREQ_CONTRACT_TRANSCRIPT" diff --git a/tools/marreq-contracts/Cargo.toml b/tools/marreq-contracts/Cargo.toml new file mode 100644 index 00000000..fdeff3f2 --- /dev/null +++ b/tools/marreq-contracts/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "marreq-contracts" +version = "0.1.0" +edition.workspace = true +license.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +sha2.workspace = true +clap = { version = "4.6", features = ["derive"] } +semver = { version = "1.0", features = ["serde"] } +serde_yaml_ng = "0.10" +jsonschema = { version = "0.60", default-features = false } +tempfile = "3" +walkdir = "2" +tree-sitter = "0.25" +tree-sitter-typescript = "0.23.2" diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs new file mode 100644 index 00000000..a5e12f7e --- /dev/null +++ b/tools/marreq-contracts/src/artifact.rs @@ -0,0 +1,326 @@ +//! Marreq artifact identity/policy. Contract hashes pin files, NOT Docker images. +use crate::{contract, coverage}; +use anyhow::{Context, Result, ensure}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, + process::Command, +}; +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Policy { + pub requires_backend_min: Version, + #[serde(skip_serializing_if = "Option::is_none")] + pub requires_backend_max: Option, +} +impl Policy { + pub fn validate(&self) -> Result<()> { + ensure!( + self.requires_backend_max + .as_ref() + .is_none_or(|max| max.cmp_precedence(&self.requires_backend_min).is_ge()), + "inverted backend compatibility range" + ); + Ok(()) + } + pub fn accepts(&self, backend: &Version) -> Result<()> { + self.validate()?; + ensure!( + backend.cmp_precedence(&self.requires_backend_min).is_ge() + && self + .requires_backend_max + .as_ref() + .is_none_or(|max| backend.cmp_precedence(max).is_le()), + "backend {backend} outside consumer policy" + ); + Ok(()) + } +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendRange { + pub min_version: Version, + pub max_version: Version, +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Bundle { + pub format: String, + pub component: String, + pub version: Version, + pub source_sha: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub variant: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub compatibility: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub frontend_compatibility: Option, + pub contract: Value, +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Artifact { + pub file: PathBuf, + pub sha256: String, + pub version: Version, + pub source_sha: String, +} +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Plan { + pub format: String, + pub variant: String, + pub backend: Artifact, + pub frontend: Artifact, + pub mcp: Artifact, +} +pub fn hash(bytes: &[u8]) -> String { + Sha256::digest(bytes) + .iter() + .map(|b| format!("{b:02x}")) + .collect() +} +fn sha(value: &str, len: usize) -> bool { + value.len() == len + && value + .bytes() + .all(|b| b.is_ascii_digit() || matches!(b, b'a'..=b'f')) +} +fn component(value: &str) -> Result<()> { + ensure!( + ["backend", "frontend", "mcp"].contains(&value), + "invalid component {value}" + ); + Ok(()) +} +pub fn variant(value: &str) -> Result<()> { + ensure!( + ["server", "cloud"].contains(&value), + "invalid deployment variant {value}" + ); + Ok(()) +} +fn identity(bundle: &Bundle) -> Result<()> { + ensure!( + bundle.format == "marreq.contract.v2", + "unsupported bundle; historical consumer dependencies must be evidenced, not inferred from current main" + ); + component(&bundle.component)?; + ensure!(sha(&bundle.source_sha, 40), "immutable source SHA required"); + if bundle.component == "backend" { + variant( + bundle + .variant + .as_deref() + .context("backend variant required")?, + )?; + ensure!( + bundle.frontend_compatibility.is_some(), + "backend frontend policy missing" + ); + } else { + ensure!( + bundle.variant.is_none(), + "consumer artifacts are deployment-independent" + ); + bundle + .compatibility + .as_ref() + .context("consumer compatibility policy missing")? + .validate()?; + } + ensure!( + !contract::operations(&bundle.contract, None)?.is_empty(), + "empty artifact contract is missing evidence" + ); + Ok(()) +} +pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> Result { + component(who)?; + let spec = contract::load(&root.join("docs/developer/openapi.yaml"))?; + let required: BTreeSet<_> = if who == "backend" { + variant(mode.as_deref().context("backend --variant required")?)?; + contract::operations(&spec, mode.as_deref())? + .into_iter() + .filter(|(_, op)| { + !op.get("x-marreq-unverified") + .is_some_and(|v| v != &Value::Bool(false)) + }) + .map(|(key, _)| key) + .collect() + } else { + coverage::scan(root, who, &spec)?.operations + }; + let selected = contract::select(&spec, &required, mode.as_deref(), true)?; + if who == "backend" { + contract::validate(&selected)?; + } else { + contract::validate_wire_contract(&selected)?; + } + let directory = if who == "mcp" { "mcp-server" } else { who }; + let mut inputs: Vec = [ + "docs/developer/openapi.yaml", + "tools/marreq-contracts", + "Cargo.toml", + "Cargo.lock", + ] + .into_iter() + .map(String::from) + .collect(); + if who == "backend" { + inputs.extend( + ["marreq-core", "marreq-cloud", "marreq-server"] + .into_iter() + .map(String::from), + ); + } else { + inputs.extend( + [ + "src", + "package.json", + "package-lock.json", + "compatibility.json", + ] + .into_iter() + .map(|path| format!("{directory}/{path}")), + ); + } + let status = Command::new("git") + .args(["status", "--porcelain", "--untracked-files=all", "--"]) + .args(&inputs) + .current_dir(root) + .output()?; + ensure!( + status.status.success() && status.stdout.is_empty(), + "commit source/contract inputs before exporting an immutable bundle" + ); + let output = Command::new("git") + .args(["rev-parse", "HEAD"]) + .current_dir(root) + .output()?; + ensure!(output.status.success(), "cannot determine source identity"); + let source_sha = String::from_utf8(output.stdout)?.trim().to_owned(); + let policy = if who != "backend" { + Some(serde_json::from_value(contract::load( + &root.join(directory).join("compatibility.json"), + )?)?) + } else { + None + }; + let frontend_compatibility = if who == "backend" { + Some(serde_json::from_value(contract::load( + &root.join("marreq-core/frontend_compatibility.json"), + )?)?) + } else { + None + }; + let bundle = Bundle { + format: "marreq.contract.v2".into(), + component: who.into(), + version, + source_sha, + variant: mode, + compatibility: policy, + frontend_compatibility, + contract: selected, + }; + identity(&bundle)?; + Ok(bundle) +} +fn read(directory: &Path, descriptor: &Artifact, who: &str) -> Result { + ensure!( + !descriptor.file.is_absolute() + && !descriptor + .file + .components() + .any(|x| matches!(x, std::path::Component::ParentDir)), + "artifact file must stay inside plan directory" + ); + let path = directory.join(&descriptor.file); + let canonical = path.canonicalize().with_context(|| { + format!( + "missing historical {who} artifact {}; refusing substitution with main", + path.display() + ) + })?; + ensure!( + canonical.starts_with(directory.canonicalize()?), + "artifact symlink escapes plan directory" + ); + let bytes = std::fs::read(canonical)?; + ensure!( + sha(&descriptor.sha256, 64) && hash(&bytes) == descriptor.sha256, + "{who}: contract SHA-256 mismatch (not an image digest)" + ); + let bundle: Bundle = + serde_json::from_slice(&bytes).with_context(|| format!("invalid {who} bundle"))?; + identity(&bundle)?; + ensure!( + bundle.component == who + && bundle.version == descriptor.version + && bundle.source_sha == descriptor.source_sha, + "{who}: artifact component/version/source SHA mismatch" + ); + contract::validate(&bundle.contract)?; + Ok(bundle) +} +pub fn verify(path: &Path, candidate: Option<(&str, &Version, &str, &Path)>) -> Result { + let plan: Plan = serde_json::from_value(contract::load(path)?)?; + ensure!( + plan.format == "marreq.deployment-contract.v2", + "unsupported deployment plan format" + ); + variant(&plan.variant)?; + let directory = path + .parent() + .filter(|p| !p.as_os_str().is_empty()) + .unwrap_or(Path::new(".")); + let backend = read(directory, &plan.backend, "backend")?; + let frontend = read(directory, &plan.frontend, "frontend")?; + let mcp = read(directory, &plan.mcp, "mcp")?; + ensure!( + backend.variant.as_deref() == Some(&plan.variant), + "backend deployment variant mismatch" + ); + if let Some((who, ver, source, file)) = candidate { + let descriptor = match who { + "backend" => &plan.backend, + "frontend" => &plan.frontend, + "mcp" => &plan.mcp, + _ => anyhow::bail!("invalid candidate component"), + }; + ensure!( + &descriptor.version == ver + && descriptor.source_sha == source + && hash(&std::fs::read(file)?) == descriptor.sha256, + "plan does not reference the source-exported tagged candidate" + ); + } + let range = backend.frontend_compatibility.as_ref().unwrap(); + ensure!( + range.min_version.cmp_precedence(&range.max_version).is_le(), + "invalid backend frontend range" + ); + ensure!( + frontend.version.cmp_precedence(&range.min_version).is_ge() + && frontend.version.cmp_precedence(&range.max_version).is_le(), + "frontend version outside backend policy" + ); + for consumer in [&frontend, &mcp] { + consumer + .compatibility + .as_ref() + .unwrap() + .accepts(&backend.version) + .with_context(|| consumer.component.clone())?; + contract::compare(&consumer.contract, &backend.contract, &plan.variant) + .with_context(|| consumer.component.clone())?; + } + Ok( + serde_json::json!({"compatible":true,"variant":plan.variant,"versions":{"backend":backend.version,"frontend":frontend.version,"mcp":mcp.version}}), + ) +} diff --git a/tools/marreq-contracts/src/contract.rs b/tools/marreq-contracts/src/contract.rs new file mode 100644 index 00000000..1a829d30 --- /dev/null +++ b/tools/marreq-contracts/src/contract.rs @@ -0,0 +1,387 @@ +//! OpenAPI selection and validation. Standards parsing/comparison is delegated +//! to pinned oasdiff; JSON instances use the jsonschema crate in offline mode. +use anyhow::{Context, Result, bail, ensure}; +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::Path, + process::Command, +}; + +pub const ENGINE_VERSION: &str = "1.34.0"; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Operation { + pub path: String, + pub method: String, +} +impl std::fmt::Display for Operation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.method.to_uppercase(), self.path) + } +} +pub fn load(path: &Path) -> Result { + serde_yaml_ng::from_str( + &std::fs::read_to_string(path) + .with_context(|| format!("missing contract evidence: {}", path.display()))?, + ) + .context("invalid JSON/YAML") +} +pub fn write(path: &Path, value: &impl Serialize) -> Result<()> { + std::fs::write(path, serde_json::to_vec_pretty(value)?).context("write contract") +} +pub fn operations(spec: &Value, variant: Option<&str>) -> Result> { + let mut result = BTreeMap::new(); + for (path, item) in spec["paths"].as_object().context("OpenAPI paths missing")? { + for (method, op) in item.as_object().context("invalid path item")? { + if !["get", "post", "put", "patch", "delete", "head", "options"] + .contains(&method.as_str()) + { + continue; + } + if variant.is_some_and(|v| { + op.get("x-marreq-variants") + .and_then(Value::as_array) + .is_some_and(|a| !a.iter().any(|x| x == v)) + }) { + continue; + } + let mut op = op.clone(); + if let Some(parameters) = item.get("parameters") { + let mut merged = parameters.as_array().context("path parameters")?.clone(); + merged.extend(op["parameters"].as_array().cloned().unwrap_or_default()); + op["parameters"] = json!(merged); + } + if op.get("security").is_none() { + op["security"] = spec.get("security").cloned().unwrap_or(json!([])); + } + result.insert( + Operation { + path: path.clone(), + method: method.clone(), + }, + op, + ); + } + } + Ok(result) +} +// Compact a consumer bundle to its operations and reachable local components. +// No dereferencing/schema comparison is implemented here. +pub fn select( + spec: &Value, + required: &BTreeSet, + variant: Option<&str>, + verified: bool, +) -> Result { + let available = operations(spec, variant)?; + let mut selected = json!({"openapi":spec["openapi"],"info":spec["info"],"servers":spec.get("servers").cloned().unwrap_or(json!([{ "url":"/" }])),"paths":{},"components":{}}); + for key in required { + let op = available.get(key).with_context(|| { + format!( + "required operation missing in {} backend: {key}", + variant.unwrap_or("selected") + ) + })?; + if verified + && op + .get("x-marreq-unverified") + .is_some_and(|v| v != &Value::Bool(false)) + { + bail!("required consumer operation has unverified wire contract: {key}"); + } + selected["paths"][&key.path][&key.method] = op.clone(); + } + // Include just referenced components, including security schemes inherited + // at the operation level. External refs are always rejected before tooling. + loop { + let mut references = BTreeSet::new(); + collect_refs(&selected, &mut references)?; + for item in selected["paths"].as_object().unwrap().values() { + for op in item.as_object().unwrap().values() { + for req in op["security"].as_array().into_iter().flatten() { + for name in req.as_object().context("security requirement")?.keys() { + references.insert(format!("#/components/securitySchemes/{name}")); + } + } + } + } + let mut changed = false; + for reference in references { + let pointer = &reference[1..]; + if selected.pointer(pointer).is_none() { + let parts: Vec<_> = pointer.trim_start_matches('/').split('/').collect(); + ensure!( + parts.len() == 3 && parts[0] == "components", + "unsupported non-component reference {reference}" + ); + selected["components"][parts[1]][parts[2].replace("~1", "/").replace("~0", "~")] = + spec.pointer(pointer) + .with_context(|| format!("missing reference {reference}"))? + .clone(); + changed = true; + } + } + if !changed { + break; + } + } + Ok(selected) +} +fn collect_refs(value: &Value, out: &mut BTreeSet) -> Result<()> { + match value { + Value::Object(map) => { + if let Some(r) = map.get("$ref") { + let r = r.as_str().context("invalid $ref")?; + ensure!( + r.starts_with("#/"), + "external references are forbidden: {r}" + ); + out.insert(r.into()); + } + for v in map.values() { + collect_refs(v, out)?; + } + } + Value::Array(a) => { + for v in a { + collect_refs(v, out)?; + } + } + _ => (), + } + Ok(()) +} +fn engine() -> Result { + let binary = std::env::var_os("OASDIFF").unwrap_or_else(|| "oasdiff".into()); + let output = Command::new(&binary).arg("--version").output().context( + "install checksum-pinned oasdiff using scripts/install-oasdiff.sh (or set OASDIFF)", + )?; + let version = String::from_utf8_lossy(&output.stdout); + ensure!( + output.status.success() + && version + .split_whitespace() + .any(|v| v.trim_start_matches('v') == ENGINE_VERSION), + "expected oasdiff {ENGINE_VERSION}, got {version}" + ); + Ok(Command::new(binary)) +} +pub fn validate(spec: &Value) -> Result<()> { + collect_refs(spec, &mut BTreeSet::new())?; + let directory = tempfile::tempdir()?; + let path = directory.path().join("contract.json"); + write(&path, spec)?; + let output = engine()? + .args([ + "validate", + "--allow-external-refs=false", + "--fail-on", + "WARN", + "--format", + "json", + ]) + .arg(path) + .output()?; + ensure!( + output.status.success(), + "OpenAPI validation failed: {}{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + Ok(()) +} +// Explicitly bound the schema dialect whose compatibility the pinned engine +// can establish. Advanced semantics are localized to selected consumer schemas, +// never silently ignored or applied to unrelated backend definitions. +fn schema_policy(schema: &Value) -> Result<()> { + let Some(map) = schema.as_object() else { + return Ok(()); + }; + let supported = [ + "$schema", + "$ref", + "$id", + "$defs", + "definitions", + "type", + "const", + "enum", + "allOf", + "anyOf", + "oneOf", + "properties", + "required", + "additionalProperties", + "items", + "minItems", + "maxItems", + "uniqueItems", + "minLength", + "maxLength", + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum", + "multipleOf", + "minProperties", + "maxProperties", + "pattern", + "format", + "title", + "description", + "default", + "examples", + "example", + "deprecated", + "readOnly", + "writeOnly", + "nullable", + "discriminator", + "externalDocs", + "xml", + "x-extensible-enum", + "x-marreq-unverified", + ]; + for key in map.keys() { + ensure!( + supported.contains(&key.as_str()), + "schema keyword {key} is outside the supported compatibility dialect; affected operation needs explicit evidence" + ); + } + ensure!( + !map.get("x-marreq-unverified") + .is_some_and(|v| v != &Value::Bool(false)), + "selected schema is unverified" + ); + for key in ["properties", "$defs", "definitions"] { + if let Some(values) = map.get(key).and_then(Value::as_object) { + for value in values.values() { + schema_policy(value)?; + } + } + } + for key in ["items", "additionalProperties"] { + if let Some(value) = map.get(key) { + schema_policy(value)?; + } + } + for key in ["allOf", "anyOf", "oneOf"] { + if let Some(values) = map.get(key).and_then(Value::as_array) { + for value in values { + schema_policy(value)?; + } + } + } + Ok(()) +} +fn supported_schemas(document: &Value) -> Result<()> { + if let Some(schemas) = document + .pointer("/components/schemas") + .and_then(Value::as_object) + { + for schema in schemas.values() { + schema_policy(schema)?; + } + } + fn inline(value: &Value) -> Result<()> { + match value { + Value::Object(map) => { + for (key, value) in map { + if key == "schema" { + schema_policy(value)?; + } else if !matches!(key.as_str(), "example" | "examples" | "default") { + inline(value)?; + } + } + } + Value::Array(values) => { + for value in values { + inline(value)?; + } + } + _ => (), + } + Ok(()) + } + inline(document) +} + +pub fn validate_wire_contract(document: &Value) -> Result<()> { + validate(document)?; + supported_schemas(document) +} + +pub fn compare(consumer: &Value, backend: &Value, variant: &str) -> Result { + crate::artifact::variant(variant)?; + let required = operations(consumer, Some(variant))?.into_keys().collect(); + let old = select(consumer, &required, Some(variant), true)?; + let new = select(backend, &required, Some(variant), true)?; + validate_wire_contract(&old)?; + validate_wire_contract(&new)?; + // These are Marreq-specific authorization semantics not standard OpenAPI + // security schemes. Compare per operation, never globally across the API. + let a = operations(&old, None)?; + let b = operations(&new, None)?; + for (key, op) in &a { + ensure!( + op.get("x-marreq-csrf") == b[key].get("x-marreq-csrf"), + "{key}: CSRF authentication requirement changed" + ); + let scopes = |v: &Value| -> Result> { + v.as_array() + .context("scope declarations must be arrays")? + .iter() + .map(|s| Ok(s.as_str().context("scope must be string")?.to_owned())) + .collect() + }; + let old = op.get("x-marreq-bearer-scopes"); + let new = b[key].get("x-marreq-bearer-scopes"); + match (old, new) { + (Some(old), Some(new)) => ensure!( + scopes(new)?.is_subset(&scopes(old)?), + "{key}: newly required Bearer scopes" + ), + (None, None) => (), + _ => anyhow::bail!("{key}: Bearer authorization evidence changed"), + } + } + let dir = tempfile::tempdir()?; + let base = dir.path().join("consumer.json"); + let revision = dir.path().join("backend.json"); + write(&base, &old)?; + write(&revision, &new)?; + let output = engine()? + .args([ + "breaking", + "--allow-external-refs=false", + "--fail-on", + "WARN", + "--format", + "json", + "--flatten-allof", + ]) + .arg(base) + .arg(revision) + .output()?; + let findings: Value = serde_json::from_slice(&output.stdout).unwrap_or(json!([])); + ensure!( + output.status.success(), + "incompatible {variant} producer/consumer contract: {findings} {}", + String::from_utf8_lossy(&output.stderr) + ); + Ok(findings) +} +pub fn validate_instance(spec: &Value, schema: &Value, instance: &Value) -> Result<()> { + let mut schema = schema.clone(); + if let Some(obj) = schema.as_object_mut() { + obj.insert("components".into(), spec["components"].clone()); + } + let validator = jsonschema::options() + .offline() + .build(&schema) + .context("invalid/offline JSON schema")?; + validator + .validate(instance) + .map_err(|e| anyhow::anyhow!("wire schema violation at {}: {}", e.instance_path(), e)) +} diff --git a/tools/marreq-contracts/src/coverage.rs b/tools/marreq-contracts/src/coverage.rs new file mode 100644 index 00000000..2a85d84f --- /dev/null +++ b/tools/marreq-contracts/src/coverage.rs @@ -0,0 +1,714 @@ +//! Conservative TypeScript call analysis using the real TS/TSX grammar. +//! Unknown URL or HTTP-method expressions fail instead of dropping dependencies. +use crate::contract::{Operation, operations}; +use anyhow::{Context, Result, bail}; +use serde::Serialize; +use serde_json::Value; +use std::{ + collections::{BTreeMap, BTreeSet, HashSet}, + path::Path, +}; +use tree_sitter::Node; + +#[derive(Debug, Serialize)] +pub struct Coverage { + pub operations: BTreeSet, + pub call_sites: usize, +} + +fn children(node: Node<'_>) -> Vec> { + let mut cursor = node.walk(); + node.named_children(&mut cursor).collect() +} +fn descend<'a>(node: Node<'a>, kind: &str, found: &mut Vec>) { + if node.kind() == kind { + found.push(node); + } + for child in children(node) { + descend(child, kind, found); + } +} +fn text<'a>(node: Node<'_>, source: &'a str) -> &'a str { + &source[node.byte_range()] +} +fn field<'a>(node: Node<'a>, name: &str) -> Result> { + node.child_by_field_name(name).context("missing AST field") +} +fn name(node: Node<'_>, source: &str) -> String { + if matches!( + node.kind(), + "await_expression" | "parenthesized_expression" | "instantiation_expression" + ) && let Some(inner) = children(node).first() + { + return name(*inner, source); + } + if node.kind() == "subscript_expression" + && let Some(index) = node + .child_by_field_name("index") + .filter(|n| n.kind() == "string") + { + return text(index, source).trim_matches(['\'', '"']).into(); + } + node.child_by_field_name("property") + .map_or_else(|| text(node, source), |n| text(n, source)) + .to_owned() +} +fn enclosing(mut node: Node<'_>) -> Node<'_> { + while let Some(parent) = node.parent() { + if matches!( + node.kind(), + "function_declaration" | "method_definition" | "arrow_function" + ) { + return node; + } + node = parent; + } + node +} +fn combine(left: Vec, right: Vec) -> Vec { + left.into_iter() + .flat_map(|a| right.iter().map(move |b| format!("{a}{b}"))) + .collect() +} + +// Only finite string construction is evaluated. Unknown parameter values become +// a segment wildcard; an entirely unknown endpoint or HTTP method is rejected. +fn values( + node: Node<'_>, + source: &str, + root: Node<'_>, + bound: &BTreeMap>, + depth: usize, +) -> Vec { + if depth > 16 { + return vec!["*".into()]; + } + let eval = |n| values(n, source, root, bound, depth + 1); + match node.kind() { + "string" => vec![text(node, source)[1..text(node, source).len() - 1].to_owned()], + "template_string" => { + let mut result = vec![String::new()]; + for child in children(node) { + let part = if child.kind() == "string_fragment" { + vec![text(child, source).to_owned()] + } else if child.kind() == "template_substitution" { + children(child) + .first() + .map_or(vec!["*".into()], |n| eval(*n)) + } else { + vec![text(child, source).to_owned()] + }; + result = combine(result, part); + if result.len() > 128 { + return vec!["*".into()]; + } + } + result + } + "ternary_expression" => ["consequence", "alternative"] + .into_iter() + .filter_map(|f| node.child_by_field_name(f)) + .flat_map(eval) + .collect(), + "binary_expression" + if node + .child_by_field_name("operator") + .is_some_and(|n| text(n, source) == "+") => + { + combine( + eval(field(node, "left").unwrap()), + eval(field(node, "right").unwrap()), + ) + } + "identifier" => { + let id = text(node, source); + if let Some(v) = bound.get(id) { + return v.clone(); + } + let scope = enclosing(node); + let mut vars = vec![]; + descend(scope, "variable_declarator", &mut vars); + if let Some(value) = vars + .into_iter() + .filter(|v| { + v.start_byte() < node.start_byte() + && v.parent() + .and_then(|p| p.child(0)) + .is_some_and(|n| n.kind() == "const") + }) + .filter(|v| { + let mut block = v.parent(); + while block.is_some_and(|p| !matches!(p.kind(), "statement_block" | "program")) + { + block = block.and_then(|p| p.parent()); + } + let mut parent = Some(node); + while let Some(p) = parent { + if Some(p) == block { + return true; + } + parent = p.parent(); + } + false + }) + .rev() + .find_map(|v| { + (v.child_by_field_name("name") + .is_some_and(|n| text(n, source) == id)) + .then(|| v.child_by_field_name("value")) + .flatten() + }) + { + return eval(value); + } + if let Some(parameters) = scope.child_by_field_name("parameters") { + for parameter in children(parameters) { + let ident = parameter + .child_by_field_name("pattern") + .or_else(|| parameter.child_by_field_name("name")); + let numeric = parameter.child_by_field_name("type").is_some_and(|t| { + text(t, source).trim().trim_start_matches(':').trim() == "number" + }); + if numeric && ident.is_some_and(|n| text(n, source) == id) { + return vec!["#".into()]; + } + } + } + vec!["*".into()] + } + "number" => vec![text(node, source).into()], + "member_expression" + if node.child_by_field_name("property").is_some_and(|n| { + let property = text(n, source); + property == "id" || property.ends_with("Id") || property.ends_with("_id") + }) => + { + vec!["#".into()] + } + "parenthesized_expression" | "as_expression" | "non_null_expression" => children(node) + .first() + .map_or(vec!["*".into()], |n| eval(*n)), + "call_expression" => { + let Ok(fun) = field(node, "function") else { + return vec!["*".into()]; + }; + let fun_name = name(fun, source); + let args = node + .child_by_field_name("arguments") + .map(children) + .unwrap_or_default(); + if matches!(fun_name.as_str(), "encodeURIComponent" | "String") { + return vec!["*".into()]; + } + if fun_name == "withQuery" { + return args.first().map_or(vec!["*".into()], |n| eval(*n)); + } + let mut defs = vec![]; + descend(root, "function_declaration", &mut defs); + descend(root, "method_definition", &mut defs); + // Arrow-valued helpers such as `const base = (projectId) => ...`. + let mut vars = vec![]; + descend(root, "variable_declarator", &mut vars); + for var in vars { + if var + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == fun_name) + && let Some(v) = var + .child_by_field_name("value") + .filter(|n| n.kind() == "arrow_function") + { + defs.push(v); + } + } + for def in defs { + if def.kind() != "arrow_function" + && !def + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == fun_name) + { + continue; + } + let mut scope = bound.clone(); + if let Some(parameters) = def.child_by_field_name("parameters") { + for (i, p) in children(parameters).into_iter().enumerate() { + let id = p + .child_by_field_name("pattern") + .or_else(|| p.child_by_field_name("name")) + .unwrap_or(p); + let default = p.child_by_field_name("value"); + scope.insert( + text(id, source).into(), + args.get(i).map_or_else( + || default.map_or(vec!["*".into()], eval), + |n| eval(*n), + ), + ); + } + } + if let Some(body) = def.child_by_field_name("body") { + let mut returns = vec![]; + if body.kind() == "statement_block" { + descend(body, "return_statement", &mut returns); + } else { + returns.push(body); + } + let result: Vec<_> = returns + .into_iter() + .flat_map(|r| { + let v = if r.kind() == "return_statement" { + children(r).first().copied() + } else { + Some(r) + }; + v.map_or(vec![], |v| values(v, source, root, &scope, depth + 1)) + }) + .collect(); + if !result.is_empty() { + return result; + } + } + } + vec!["*".into()] + } + _ => vec!["*".into()], + } +} + +fn methods(init: Option>, source: &str) -> Result> { + let Some(init) = init else { + return Ok(vec!["get".into()]); + }; + if init.kind() == "call_expression" { + let fun = name(field(init, "function")?, source); + if fun == "write" { + let args = children(field(init, "arguments")?); + if let Some(n) = args.first().filter(|n| n.kind() == "string") { + return Ok(vec![ + text(*n, source)[1..text(*n, source).len() - 1].to_lowercase(), + ]); + } + } + bail!("unresolved request options: {}", text(init, source)); + } + if init.kind() != "object" { + bail!( + "request options need an explicit HTTP method: {}", + text(init, source) + ); + } + anyhow::ensure!( + !children(init).iter().any(|n| n.kind() == "spread_element"), + "request option spreads need explicit method evidence" + ); + for p in children(init).into_iter().filter(|n| n.kind() == "pair") { + if text(field(p, "key")?, source).trim_matches(['\'', '"']) == "method" { + let v = field(p, "value")?; + return match v.kind() { + "string" => Ok(vec![ + text(v, source)[1..text(v, source).len() - 1].to_lowercase(), + ]), + // Both branch methods are required; dynamic arbitrary methods fail. + "ternary_expression" => ["consequence", "alternative"] + .into_iter() + .map(|f| { + let n = field(v, f)?; + if n.kind() != "string" { + bail!("unresolved method") + }; + Ok(text(n, source)[1..text(n, source).len() - 1].to_lowercase()) + }) + .collect(), + _ => bail!("unresolved HTTP method: {}", text(v, source)), + }; + } + } + Ok(vec!["get".into()]) +} + +// Intersection of segment globs. '*' never crosses '/', so a collection call +// cannot accidentally require every nested resource beneath it. +pub fn intersects(a: &str, b: &str) -> bool { + fn go(a: &[u8], b: &[u8], i: usize, j: usize, seen: &mut HashSet<(usize, usize)>) -> bool { + if !seen.insert((i, j)) { + return false; + } + if i == a.len() && j == b.len() { + return true; + } + // '#' is a nonempty numeric identifier. It keeps a typed id lookup + // from accidentally selecting a reserved sibling such as /index_status. + match (a.get(i), b.get(j)) { + (Some(b'#'), Some(b'#')) => return go(a, b, i + 1, j + 1, seen), + (Some(b'#'), Some(b'*')) => { + return go(a, b, i + 1, j, seen) || go(a, b, i, j + 1, seen); + } + (Some(b'*'), Some(b'#')) => { + return go(a, b, i, j + 1, seen) || go(a, b, i + 1, j, seen); + } + (Some(b'#'), Some(d)) if d.is_ascii_digit() => { + return go(a, b, i + 1, j + 1, seen) || go(a, b, i, j + 1, seen); + } + (Some(d), Some(b'#')) if d.is_ascii_digit() => { + return go(a, b, i + 1, j + 1, seen) || go(a, b, i + 1, j, seen); + } + (Some(b'#'), _) | (_, Some(b'#')) => return false, + _ => (), + } + if a.get(i) == Some(&b'*') { + return go(a, b, i + 1, j, seen) + || (j < b.len() && b[j] != b'/' && go(a, b, i, j + 1, seen)); + } + if b.get(j) == Some(&b'*') { + return go(a, b, i, j + 1, seen) + || (i < a.len() && a[i] != b'/' && go(a, b, i + 1, j, seen)); + } + i < a.len() && j < b.len() && a[i] == b[j] && go(a, b, i + 1, j + 1, seen) + } + go(a.as_bytes(), b.as_bytes(), 0, 0, &mut HashSet::new()) +} +fn path_glob(path: &str) -> String { + let mut out = String::new(); + let mut param = false; + for c in path.chars() { + if c == '{' { + param = true; + out.push('*'); + } else if c == '}' { + param = false; + } else if !param { + out.push(c); + } + } + out +} + +pub fn analyze( + source: &str, + tsx: bool, + declared: &BTreeMap, + transport: bool, +) -> Result { + analyze_file(source, tsx, declared, transport, false) +} + +fn analyze_file( + source: &str, + tsx: bool, + declared: &BTreeMap, + transport: bool, + user_content_links: bool, +) -> Result { + let mut parser = tree_sitter::Parser::new(); + parser.set_language(&if tsx { + tree_sitter_typescript::LANGUAGE_TSX.into() + } else { + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into() + })?; + let tree = parser.parse(source, None).context("parse TypeScript")?; + let root = tree.root_node(); + if root.has_error() { + let mut nodes = vec![]; + descend(root, "ERROR", &mut nodes); + // The upstream TSX grammar treats a bare ampersand in JSX text as an + // error although TypeScript accepts it. No executable expression is + // inside such a text fragment; every other parse error fails closed. + let harmless = |n: &Node<'_>| { + tsx && n.parent().is_some_and(|p| p.kind() == "jsx_element") + && text(*n, source).starts_with('&') + && !text(*n, source).contains(['{', '}', '<', '>']) + }; + if nodes.is_empty() || !nodes.iter().all(harmless) { + bail!( + "TypeScript syntax errors prevent reliable coverage analysis: {:?}", + nodes + .iter() + .map(|n| (n.start_position().row + 1, text(*n, source))) + .collect::>() + ); + } + } + let mut calls = vec![]; + descend(root, "call_expression", &mut calls); + let mut result = Coverage { + operations: BTreeSet::new(), + call_sites: 0, + }; + let known = |name: &str| { + matches!( + name, + "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" + ) + }; + let mut aliases = BTreeMap::new(); + let mut imports = vec![]; + descend(root, "import_specifier", &mut imports); + for import in imports { + if let (Some(original), Some(alias)) = ( + import.child_by_field_name("name"), + import.child_by_field_name("alias"), + ) && known(text(original, source)) + { + aliases.insert( + text(alias, source).to_owned(), + text(original, source).to_owned(), + ); + } + } + let mut variables = vec![]; + descend(root, "variable_declarator", &mut variables); + for var in variables { + if let (Some(id), Some(value)) = ( + var.child_by_field_name("name"), + var.child_by_field_name("value"), + ) { + let original = name(value, source); + let original = aliases.get(&original).cloned().unwrap_or(original); + if known(&original) { + aliases.insert(text(id, source).to_owned(), original); + } + } + } + for call in calls { + let fun = field(call, "function")?; + let fun_name = name(fun, source); + let fun_name = aliases.get(&fun_name).cloned().unwrap_or(fun_name); + if !matches!( + fun_name.as_str(), + "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" + ) { + let args = call + .child_by_field_name("arguments") + .map(children) + .unwrap_or_default(); + let mut endpoints = vec![]; + if let Some(first) = args.first() { + endpoints.extend(values(*first, source, root, &BTreeMap::new(), 0)); + if first.kind() == "object" { + for pair in children(*first).into_iter().filter(|n| n.kind() == "pair") { + if pair + .child_by_field_name("key") + .is_some_and(|n| matches!(text(n, source), "url" | "path" | "uri")) + && let Some(value) = pair.child_by_field_name("value") + { + endpoints.extend(values(value, source, root, &BTreeMap::new(), 0)); + } + } + } + } + if endpoints.iter().any(|p| p.contains("/api/")) + && !matches!( + fun_name.as_str(), + "base" | "project" | "useCases" | "withQuery" + ) + { + bail!( + "unclassified use of an API URL by {fun_name} at line {}; add an explicit transport adapter", + call.start_position().row + 1 + ); + } + continue; + } + // Exactly the shared transports forward arbitrary paths. Their callers + // are checked here; adding another forwarding layer is not auto-excluded. + if transport && fun_name == "fetch" { + let owner = enclosing(call) + .child_by_field_name("name") + .map(|n| text(n, source)); + if matches!( + owner, + Some("request" | "fetchJson" | "fetchBlob" | "fetchDownload") + ) { + continue; + } + } + if fun_name == "download" { + let mut defs = vec![]; + descend(root, "function_declaration", &mut defs); + let forwards = defs + .into_iter() + .filter(|d| { + d.child_by_field_name("name") + .is_some_and(|n| text(n, source) == "download") + }) + .any(|d| { + let mut inner = vec![]; + descend(d, "call_expression", &mut inner); + inner.into_iter().any(|c| { + c.child_by_field_name("function") + .is_some_and(|f| name(f, source) == "fetchDownload") + }) + }); + if !forwards { + continue; + } + } + let args = children(field(call, "arguments")?); + let Some(path) = args.first() else { + bail!("HTTP call has no path") + }; + let mut paths = values(*path, source, root, &BTreeMap::new(), 0); + // A forwarding download helper is covered at each concrete call site. + if fun_name == "fetchDownload" + && paths == ["*"] + && enclosing(call) + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == "download") + { + continue; + } + paths = paths + .into_iter() + .map(|p| p.split('?').next().unwrap().to_owned()) + .collect(); + paths.sort(); + paths.dedup(); + let method = methods( + if fun_name == "download" { + None + } else { + args.get(1).copied() + }, + source, + ) + .with_context(|| format!("line {}", call.start_position().row + 1))?; + for path in paths { + if !path.starts_with("/api/") { + bail!( + "unresolved HTTP dependency at line {}: {}", + call.start_position().row + 1, + text(*args.first().unwrap(), source) + ); + } + for method in &method { + let found: Vec<_> = declared + .keys() + .filter(|op| op.method == *method && intersects(&path, &path_glob(&op.path))) + .cloned() + .collect(); + if found.is_empty() { + bail!( + "undocumented consumer operation: {} {} at line {}", + method, + path, + call.start_position().row + 1 + ); + } + result.operations.extend(found); + } + } + result.call_sites += 1; + } + // Browser navigation to fixed API paths is also a consumer dependency. + let mut attributes = vec![]; + descend(root, "jsx_attribute", &mut attributes); + for attribute in attributes { + let parts = children(attribute); + if parts.first().is_none_or(|n| text(*n, source) != "href") { + continue; + } + let Some(value) = parts.get(1) else { + continue; + }; + let value = if value.kind() == "jsx_expression" { + children(*value).first().copied().context("empty href")? + } else { + *value + }; + if user_content_links && text(value, source) == "n.href" { + continue; + } + for path in values(value, source, root, &BTreeMap::new(), 0) { + let path = path.split('?').next().unwrap(); + if path == "*" { + bail!( + "unresolved browser navigation at line {}", + attribute.start_position().row + 1 + ); + } + if !path.starts_with("/api/") { + continue; + } + let keys: Vec<_> = declared + .keys() + .filter(|key| key.method == "get" && intersects(path, &path_glob(&key.path))) + .cloned() + .collect(); + if keys.is_empty() { + bail!("undocumented browser API dependency {path}"); + } + result.operations.extend(keys); + } + } + Ok(result) +} + +pub fn scan(root: &Path, component: &str, spec: &Value) -> Result { + anyhow::ensure!( + matches!(component, "frontend" | "mcp"), + "consumer must be frontend or mcp" + ); + let directory = root.join(if component == "frontend" { + "frontend/src" + } else { + "mcp-server/src" + }); + let declared = operations(spec, None)?; + let mut result = Coverage { + operations: BTreeSet::new(), + call_sites: 0, + }; + for entry in walkdir::WalkDir::new(directory) { + let entry = entry?; + let path = entry.path(); + if !entry.file_type().is_file() + || !matches!( + path.extension().and_then(|x| x.to_str()), + Some("ts" | "tsx") + ) + { + continue; + } + let relative = path.strip_prefix(root)?.to_string_lossy(); + if relative.contains("/__tests__/") + || relative.ends_with(".test.ts") + || relative.ends_with("generated-contract.ts") + { + continue; + } + let source = std::fs::read_to_string(path)?; + let transport = + relative == "frontend/src/api/transport.ts" || relative == "mcp-server/src/client.ts"; + let coverage = analyze_file( + &source, + path.extension().is_some_and(|x| x == "tsx"), + &declared, + transport, + relative == "frontend/src/components/StatementText.tsx", + ) + .with_context(|| relative.to_string())?; + result.call_sites += coverage.call_sites; + result.operations.extend(coverage.operations); + } + // Protocol navigation/discovery isn't fetch(): explicit, small, tested roots. + let protocol = if component == "frontend" { + vec!["auth_auth_external_start", "auth_auth_external_callback"] + } else { + vec![ + "oauth_authorization_server_metadata", + "oauth_protected_resource_metadata_mcp", + "oauth_register", + "oauth_authorize_page", + "oauth_authorize_decision", + "oauth_token", + ] + }; + for id in protocol { + let key = declared + .iter() + .find(|(_, v)| v["operationId"] == id) + .map(|(k, _)| k.clone()) + .with_context(|| format!("missing protocol operation {id}"))?; + result.operations.insert(key); + } + Ok(result) +} diff --git a/tools/marreq-contracts/src/lib.rs b/tools/marreq-contracts/src/lib.rs new file mode 100644 index 00000000..d0ca8ac9 --- /dev/null +++ b/tools/marreq-contracts/src/lib.rs @@ -0,0 +1,4 @@ +pub mod artifact; +pub mod contract; +pub mod coverage; +pub mod traffic; diff --git a/tools/marreq-contracts/src/main.rs b/tools/marreq-contracts/src/main.rs new file mode 100644 index 00000000..0f47dc2c --- /dev/null +++ b/tools/marreq-contracts/src/main.rs @@ -0,0 +1,204 @@ +use clap::{Parser, Subcommand}; +use marreq_contracts::{artifact, contract, coverage, traffic}; +use std::path::PathBuf; +#[derive(Parser)] +struct Cli { + #[command(subcommand)] + command: Action, +} +#[derive(Subcommand)] +enum Action { + Coverage { + #[arg(long, default_value = ".")] + root: PathBuf, + #[arg(long, default_value = "docs/developer/openapi.yaml")] + spec: PathBuf, + #[arg(long)] + component: String, + }, + Validate { + #[arg(long, default_value = "docs/developer/openapi.yaml")] + spec: PathBuf, + #[arg(long, default_value = ".")] + root: PathBuf, + #[arg(long)] + document_only: bool, + }, + Bundle { + #[arg(long, default_value = ".")] + root: PathBuf, + #[arg(long)] + component: String, + #[arg(long)] + version: semver::Version, + #[arg(long)] + variant: Option, + #[arg(long)] + output: PathBuf, + }, + Verify { + #[arg(long)] + manifest: PathBuf, + #[arg(long,requires_all=["candidate_version","candidate_source_sha","candidate_bundle"])] + candidate_component: Option, + #[arg(long)] + candidate_version: Option, + #[arg(long)] + candidate_source_sha: Option, + #[arg(long)] + candidate_bundle: Option, + }, + VerifyTraffic { + transcript: PathBuf, + #[arg(long, default_value = "docs/developer/openapi.yaml")] + spec: PathBuf, + }, + Select { + #[arg(long, default_value = ".")] + root: PathBuf, + #[arg(long)] + component: String, + #[arg(long, default_value = "docs/developer/openapi.yaml")] + spec: PathBuf, + #[arg(long)] + output: PathBuf, + #[arg(long)] + allow_unverified: bool, + }, + Compare { + consumer: PathBuf, + backend: PathBuf, + #[arg(long)] + variant: String, + }, +} +fn run() -> anyhow::Result { + match Cli::parse().command { + Action::Coverage { + root, + spec, + component, + } => Ok(serde_json::to_value(coverage::scan( + &root, + &component, + &contract::load(&spec)?, + )?)?), + Action::Bundle { + root, + component, + version, + variant, + output, + } => { + let bundle = artifact::export(&root, &component, version, variant)?; + contract::write(&output, &bundle)?; + Ok( + serde_json::json!({"file":output,"sha256":artifact::hash(&std::fs::read(&output)?),"source_sha":bundle.source_sha,"version":bundle.version}), + ) + } + Action::Verify { + manifest, + candidate_component, + candidate_version, + candidate_source_sha, + candidate_bundle, + } => { + let any = candidate_component.is_some() + || candidate_version.is_some() + || candidate_source_sha.is_some() + || candidate_bundle.is_some(); + let all = candidate_component.is_some() + && candidate_version.is_some() + && candidate_source_sha.is_some() + && candidate_bundle.is_some(); + anyhow::ensure!( + !any || all, + "candidate verification requires component, version, source SHA and source-exported bundle together" + ); + let candidate = candidate_component.as_deref().map(|who| { + ( + who, + candidate_version.as_ref().unwrap(), + candidate_source_sha.as_deref().unwrap(), + candidate_bundle.as_deref().unwrap(), + ) + }); + artifact::verify(&manifest, candidate) + } + Action::VerifyTraffic { transcript, spec } => { + traffic::verify(&contract::load(&spec)?, &transcript) + } + Action::Select { + root, + component, + spec, + output, + allow_unverified, + } => { + let spec = contract::load(&spec)?; + let coverage = coverage::scan(&root, &component, &spec)?; + contract::write( + &output, + &contract::select(&spec, &coverage.operations, None, !allow_unverified)?, + )?; + Ok( + serde_json::json!({"component":component,"operations":coverage.operations.len(),"output":output}), + ) + } + Action::Validate { + spec, + root, + document_only, + } => { + let spec = contract::load(&spec)?; + contract::validate(&spec)?; + for dir in ["frontend", "mcp-server"] { + let p: artifact::Policy = serde_json::from_value(contract::load( + &root.join(dir).join("compatibility.json"), + )?)?; + p.validate()?; + } + let range: artifact::FrontendRange = serde_json::from_value(contract::load( + &root.join("marreq-core/frontend_compatibility.json"), + )?)?; + anyhow::ensure!( + range.min_version.cmp_precedence(&range.max_version).is_le(), + "invalid backend frontend compatibility range" + ); + let mut counts = serde_json::Map::new(); + if !document_only { + for who in ["frontend", "mcp"] { + let c = coverage::scan(&root, who, &spec)?; + let selected = contract::select(&spec, &c.operations, None, true)?; + contract::validate_wire_contract(&selected)?; + counts.insert(who.into(), serde_json::json!(c.operations.len())); + } + } + Ok(serde_json::json!({"valid":true,"consumer_operations":counts})) + } + Action::Compare { + consumer, + backend, + variant, + } => { + let findings = contract::compare( + &contract::load(&consumer)?, + &contract::load(&backend)?, + &variant, + )?; + Ok(serde_json::json!({"compatible":true,"findings":findings})) + } + } +} +fn main() { + match run() { + Ok(value) => println!("{}", serde_json::to_string_pretty(&value).unwrap()), + Err(e) => { + println!( + "{}", + serde_json::json!({"compatible":false,"error":format!("{e:#}")}) + ); + std::process::exit(1); + } + } +} diff --git a/tools/marreq-contracts/src/traffic.rs b/tools/marreq-contracts/src/traffic.rs new file mode 100644 index 00000000..03f73598 --- /dev/null +++ b/tools/marreq-contracts/src/traffic.rs @@ -0,0 +1,103 @@ +//! Real request/response evidence collected by the established Rocket test. +use crate::{contract, coverage}; +use anyhow::{Context, Result, ensure}; +use serde_json::{Value, json}; +use std::{collections::BTreeSet, path::Path}; +pub fn verify(spec: &Value, path: &Path) -> Result { + let mut checked = 0; + let mut unverified = 0; + let mut seen = BTreeSet::new(); + for line in std::fs::read_to_string(path)? + .lines() + .filter(|l| !l.trim().is_empty()) + { + let record: Value = serde_json::from_str(line)?; + let mode = record["variant"].as_str().unwrap_or("server"); + crate::artifact::variant(mode)?; + let operations = contract::operations(spec, Some(mode))?; + let actual = record["path"] + .as_str() + .context("request path")? + .split('?') + .next() + .unwrap(); + let method = record["method"] + .as_str() + .context("HTTP method")? + .to_lowercase(); + let mut matches: Vec<_> = operations + .iter() + .filter(|(key, _)| { + key.method == method && coverage::intersects(actual, &route_pattern(&key.path)) + }) + .collect(); + matches.sort_by_key(|(key, _)| key.path.matches('{').count()); + let (key, op) = matches + .first() + .context("actual HTTP call missing in declared contract")?; + if op + .get("x-marreq-unverified") + .is_some_and(|v| v != &Value::Bool(false)) + { + unverified += 1; + continue; + } + let status = record["status"] + .as_u64() + .context("response status")? + .to_string(); + let response = op["responses"] + .get(&status) + .or_else(|| op["responses"].get("default")) + .with_context(|| format!("{key}: undocumented actual HTTP status {status}"))?; + let response = if let Some(r) = response["$ref"].as_str() { + spec.pointer(&r[1..]).context("response ref")? + } else { + response + }; + if let Some(schema) = response.pointer("/content/application~1json/schema") { + contract::validate_instance(spec, schema, &record["response"])?; + } + if status.starts_with('2') && !record["request"].is_null() { + let schema = op + .pointer("/requestBody/content/application~1json/schema") + .with_context(|| format!("{key}: undeclared JSON body"))?; + contract::validate_instance(spec, schema, &record["request"])?; + } + checked += 1; + seen.insert((key.method.clone(), key.path.clone(), status)); + } + for (method, path, status) in [ + ("get", "/api/meta/build", "200"), + ("get", "/api/meta/deployment", "200"), + ("post", "/api/projects/{project_id}/use-cases", "201"), + ( + "put", + "/api/projects/{project_id}/use-cases/{id}/draft", + "200", + ), + ("get", "/api/projects/{project_id}/use-cases", "403"), + ] { + ensure!( + seen.contains(&(method.into(), path.into(), status.into())), + "critical real-backend evidence missing: {method} {path} {status}" + ); + } + Ok(json!({"validated_exchanges":checked,"unverified_exchanges":unverified})) +} +fn route_pattern(path: &str) -> String { + let mut out = String::new(); + let mut param = false; + for c in path.chars() { + match c { + '{' => { + param = true; + out.push('*'); + } + '}' => param = false, + _ if !param => out.push(c), + _ => (), + } + } + out +} diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs new file mode 100644 index 00000000..036b940c --- /dev/null +++ b/tools/marreq-contracts/tests/compatibility.rs @@ -0,0 +1,459 @@ +use marreq_contracts::{ + artifact::{self, Artifact, Bundle, FrontendRange, Plan, Policy}, + contract::{self, Operation}, + coverage, +}; +use serde_json::{Value, json}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::Path, + process::Command, +}; +fn spec() -> Value { + json!({"openapi":"3.1.0","info":{"title":"fixture","version":"1.0.0"},"paths":{"/api/things":{"post":{"operationId":"create","security":[{"cookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"}},"required":["title"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"state":{"type":"string","enum":["draft","approved"]}},"required":["id","state"],"additionalProperties":true}}}}}}}},"components":{"securitySchemes":{"cookie":{"type":"apiKey","in":"cookie","name":"session"}}}}) +} +fn request(s: &mut Value) -> &mut Value { + &mut s["paths"]["/api/things"]["post"]["requestBody"]["content"]["application/json"]["schema"] +} +fn response(s: &mut Value) -> &mut Value { + &mut s["paths"]["/api/things"]["post"]["responses"]["200"]["content"]["application/json"]["schema"] +} +#[test] +fn valid_openapi() { + contract::validate(&spec()).unwrap(); +} +#[test] +fn missing_endpoint() { + let mut new = spec(); + new["paths"] = json!({}); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn required_response_removed() { + let mut new = spec(); + response(&mut new)["required"] = json!(["state"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn response_type_changed() { + let mut new = spec(); + response(&mut new)["properties"]["id"] = json!({"type":"string"}); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn new_required_request() { + let mut new = spec(); + request(&mut new)["properties"]["reason"] = json!({"type":"string"}); + request(&mut new)["required"] = json!(["title", "reason"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn additive_request_and_response() { + let mut new = spec(); + request(&mut new)["properties"]["optional"] = json!({"type":"integer"}); + response(&mut new)["properties"]["extra"] = json!({"type":"boolean"}); + contract::compare(&spec(), &new, "server").unwrap(); +} +#[test] +fn response_enum_extension() { + let mut new = spec(); + response(&mut new)["properties"]["state"]["enum"] = json!(["draft", "approved", "reviewed"]); + assert!(contract::compare(&spec(), &new, "cloud").is_err()); +} +#[test] +fn request_enum_restriction() { + let mut old = spec(); + let mut new = spec(); + request(&mut old)["properties"]["title"]["enum"] = json!(["a", "b"]); + request(&mut new)["properties"]["title"]["enum"] = json!(["a"]); + assert!(contract::compare(&old, &new, "cloud").is_err()); +} +#[test] +fn authentication_requirement() { + let mut new = spec(); + new["components"]["securitySchemes"]["bearer"] = json!({"type":"http","scheme":"bearer"}); + new["paths"]["/api/things"]["post"]["security"] = json!([{"bearer":[]}]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn unrelated_security_scheme_is_not_global_coupling() { + let mut new = spec(); + new["components"]["securitySchemes"]["unrelated"] = json!({"type":"http","scheme":"bearer"}); + contract::compare(&spec(), &new, "server").unwrap(); +} +#[test] +fn per_operation_scope_change() { + let mut new = spec(); + new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:write"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn unrelated_backend_endpoint_can_change() { + let mut old = spec(); + old["paths"]["/api/unrelated"] = json!({"get":{"responses":{"200":{"description":"old"}}}}); + let mut new = old.clone(); + new["paths"]["/api/unrelated"] = + json!({"delete":{"responses":{"204":{"description":"different"}}}}); + let required = BTreeSet::from([Operation { + method: "post".into(), + path: "/api/things".into(), + }]); + let consumer = contract::select(&old, &required, None, true).unwrap(); + contract::compare(&consumer, &new, "server").unwrap(); +} +#[test] +fn cloud_operation_not_required_on_server() { + let mut consumer = spec(); + consumer["paths"]["/api/register"] = + json!({"post":{"x-marreq-variants":["cloud"],"responses":{"200":{"description":"ok"}}}}); + contract::compare(&consumer, &spec(), "server").unwrap(); + assert!(contract::compare(&consumer, &spec(), "cloud").is_err()); +} +#[test] +fn required_unverified_operation_fails() { + let mut new = spec(); + new["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} +#[test] +fn external_refs_fail_without_network() { + let mut new = spec(); + response(&mut new)["properties"]["id"] = json!({"$ref":"https://example.test/schema"}); + assert!( + contract::validate(&new) + .unwrap_err() + .to_string() + .contains("external references") + ); +} +#[test] +fn actual_json_instances_are_checked() { + let schema = response(&mut spec()).clone(); + contract::validate_instance(&spec(), &schema, &json!({"id":1,"state":"draft"})).unwrap(); + assert!(contract::validate_instance(&spec(), &schema, &json!({"state":"draft"})).is_err()); + assert!( + contract::validate_instance(&spec(), &schema, &json!({"id":"wrong","state":"draft"})) + .is_err() + ); +} +#[test] +fn invalid_policy() { + for value in [ + json!({}), + json!({"requires_backend_min":"1.x"}), + json!({"requires_backend_min":"2.0.0","requires_backend_max":"1.0.0"}), + json!({"requires_backend_min":"1.0.0","typo":"x"}), + ] { + let policy = serde_json::from_value::(value); + assert!(policy.is_err() || policy.unwrap().validate().is_err()); + } +} +fn plan( + dir: &Path, + backend_version: &str, + mcp_version: &str, + incompatible: bool, +) -> std::path::PathBuf { + let mut descriptors = BTreeMap::new(); + for (who, version) in [ + ("backend", backend_version), + ("frontend", "0.2.1"), + ("mcp", mcp_version), + ] { + let mut document = spec(); + if incompatible && who == "backend" { + response(&mut document)["required"] = json!(["state"]); + } + let bundle = Bundle { + format: "marreq.contract.v2".into(), + component: who.into(), + version: version.parse().unwrap(), + source_sha: "a".repeat(40), + variant: (who == "backend").then(|| "server".into()), + compatibility: (who != "backend").then(|| Policy { + requires_backend_min: "0.2.0".parse().unwrap(), + requires_backend_max: Some("0.2.99".parse().unwrap()), + }), + frontend_compatibility: (who == "backend").then(|| FrontendRange { + min_version: "0.2.0".parse().unwrap(), + max_version: "0.3.0".parse().unwrap(), + }), + contract: document, + }; + let filename = format!("{who}.json"); + contract::write(&dir.join(&filename), &bundle).unwrap(); + descriptors.insert( + who, + Artifact { + file: filename.into(), + sha256: artifact::hash(&std::fs::read(dir.join(format!("{who}.json"))).unwrap()), + version: bundle.version, + source_sha: bundle.source_sha, + }, + ); + } + let plan = Plan { + format: "marreq.deployment-contract.v2".into(), + variant: "server".into(), + backend: descriptors.remove("backend").unwrap(), + frontend: descriptors.remove("frontend").unwrap(), + mcp: descriptors.remove("mcp").unwrap(), + }; + let path = dir.join("plan.json"); + contract::write(&path, &plan).unwrap(); + path +} +#[test] +fn compatible_backend_only_and_mcp_only_updates() { + let dir = tempfile::tempdir().unwrap(); + for (b, m) in [("0.2.4", "0.3.0"), ("0.2.3", "0.3.1")] { + let path = plan(dir.path(), b, m, false); + assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); + } +} +#[test] +fn incompatible_backend_only_update() { + let dir = tempfile::tempdir().unwrap(); + assert!(artifact::verify(&plan(dir.path(), "0.2.4", "0.3.0", true), None).is_err()); +} +#[test] +fn missing_historical_artifact() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + std::fs::remove_file(dir.path().join("mcp.json")).unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("missing historical") + ); +} +#[test] +fn descriptor_source_and_version_mismatch() { + let dir = tempfile::tempdir().unwrap(); + for key in ["source_sha", "version"] { + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let mut p = contract::load(&path).unwrap(); + p["mcp"][key] = json!(if key == "source_sha" { + "b".repeat(40) + } else { + "0.3.9".into() + }); + contract::write(&path, &p).unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("mismatch") + ); + } +} +#[test] +fn tampered_contract_hash() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + std::fs::write(dir.path().join("mcp.json"), "{}").unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("SHA-256 mismatch") + ); +} +#[test] +fn cli_exit_codes_and_json_diagnostics() { + let dir = tempfile::tempdir().unwrap(); + for bad in [false, true] { + let path = plan(dir.path(), "0.2.4", "0.3.0", bad); + let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) + .arg("verify") + .arg("--manifest") + .arg(path) + .output() + .unwrap(); + assert_eq!(result.status.success(), !bad); + let body: Value = serde_json::from_slice(&result.stdout).unwrap(); + assert_eq!(body["compatible"], !bad); + } +} +#[test] +fn type_script_grammar_resolves_helpers_and_union_methods() { + let mut document = spec(); + document["paths"]["/api/projects/{project_id}/things/{id}"] = json!({"get":{"responses":{"200":{"description":"ok"}}},"delete":{"responses":{"204":{"description":"ok"}}}}); + let ops = contract::operations(&document, None).unwrap(); + let source = "const base = (id: number) => `/api/projects/${id}/things`; function read(id: number) { return fetchJson(`${base(1)}/${id}`, {method: remove ? 'DELETE' : 'GET'}); }"; + let coverage = coverage::analyze(source, false, &ops, false).unwrap(); + assert_eq!(coverage.operations.len(), 2); +} +#[test] +fn new_unmapped_call_cannot_bypass_coverage() { + let ops = contract::operations(&spec(), None).unwrap(); + assert!(coverage::analyze("fetch('/api/new-api')", false, &ops, false).is_err()); + assert!(coverage::analyze("fetch(url)", false, &ops, false).is_err()); +} +#[test] +fn dynamic_unverified_dependency_is_rejected() { + let mut doc = spec(); + doc["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); + let coverage = coverage::analyze( + "fetchJson('/api/things', {method:'POST'})", + false, + &contract::operations(&doc, None).unwrap(), + false, + ) + .unwrap(); + assert!(contract::select(&doc, &coverage.operations, None, true).is_err()); +} +#[test] +fn segment_globs_do_not_capture_unrelated_children() { + assert!(!coverage::intersects( + "/api/things/*", + "/api/things/*/versions" + )); + assert!(coverage::intersects("/api/things/*.pdf", "/api/things/*")); +} + +#[test] +fn await_generic_calls_are_dependencies() { + let coverage = coverage::analyze( + "async function f(){ return await fetchJson('/api/things',{method:'POST'}); }", + false, + &contract::operations(&spec(), None).unwrap(), + false, + ) + .unwrap(); + assert_eq!(coverage.call_sites, 1); + assert_eq!(coverage.operations.len(), 1); +} +#[test] +fn browser_navigation_is_a_dependency() { + let mut document = spec(); + document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); + let ops = contract::operations(&document, None).unwrap(); + assert_eq!( + coverage::analyze( + "const a=Reports & exports", + true, + &ops, + false + ) + .unwrap() + .operations + .len(), + 1 + ); + assert!( + coverage::analyze( + "const a=link", + true, + &ops, + false + ) + .is_err() + ); +} +#[test] +fn unsupported_schema_semantics_fail_locally() { + let mut new = spec(); + response(&mut new)["unevaluatedProperties"] = json!(false); + assert!( + contract::compare(&spec(), &new, "server") + .unwrap_err() + .to_string() + .contains("unevaluatedProperties") + ); +} +#[test] +fn fewer_required_scopes_are_compatible() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = + json!(["things:read", "things:write"]); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:read"]); + contract::compare(&old, &new, "server").unwrap(); +} + +#[test] +fn numeric_resource_ids_do_not_require_reserved_siblings() { + let mut document = spec(); + document["paths"]["/api/things/{id}"] = + json!({"get":{"responses":{"200":{"description":"ok"}}}}); + document["paths"]["/api/things/internal_stats"] = + json!({"get":{"responses":{"200":{"description":"unused"}}}}); + let coverage = coverage::analyze( + "function get(id:number){return fetchJson(`/api/things/${id}`);}", + false, + &contract::operations(&document, None).unwrap(), + false, + ) + .unwrap(); + assert_eq!(coverage.operations.len(), 1); + assert!(coverage::intersects("/api/things/#", "/api/things/123")); + assert!(!coverage::intersects( + "/api/things/#", + "/api/things/internal_stats" + )); +} + +#[test] +fn aliases_and_unrecognized_clients_cannot_hide_new_calls() { + let ops = contract::operations(&spec(), None).unwrap(); + for source in [ + "const f=fetch; f('/api/missing');", + "import {fetchJson as api} from './transport'; api('/api/missing');", + "axios.get('/api/missing');", + "axios({url:'/api/missing'});", + ] { + assert!( + coverage::analyze(source, false, &ops, false).is_err(), + "{source}" + ); + } + assert!(coverage::analyze("function f(url:string){if(true){const url='/api/things';}return fetchJson(url,{method:'POST'});}",false,&ops,false).is_err()); + assert!( + coverage::analyze( + "let path='/api/things';path=external;fetchJson(path,{method:'POST'});", + false, + &ops, + false + ) + .is_err() + ); +} + +#[test] +fn semver_build_metadata_does_not_change_policy_precedence() { + Policy { + requires_backend_min: "0.2.4".parse().unwrap(), + requires_backend_max: Some("0.2.4".parse().unwrap()), + } + .accepts(&"0.2.4+source.sha".parse().unwrap()) + .unwrap(); +} + +#[test] +fn unknown_schema_keywords_are_not_silently_accepted() { + let mut new = spec(); + response(&mut new)["customLimit"] = json!(1); + assert!( + contract::compare(&spec(), &new, "server") + .unwrap_err() + .to_string() + .contains("customLimit") + ); +} +#[test] +fn relative_plan_paths_work() { + let dir = tempfile::tempdir().unwrap(); + plan(dir.path(), "0.2.4", "0.3.0", false); + let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) + .current_dir(dir.path()) + .args(["verify", "--manifest", "plan.json"]) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stdout) + ); +} From 701082e19df9f139e85adc906fe24e4beacfabc5 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 3 Nov 2026 15:34:29 +0100 Subject: [PATCH 03/18] fix(deps): update calamine for RUSTSEC-2026-0436 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43b36115..c72f9d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,9 +606,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "calamine" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ca341b94bc4b29e08970b1a03914fad2dfe177e2ecb67fcd6de9046004aafb" +checksum = "f431b67cba6122a1f7d26b7adafc71bbdf07026095fe811f5efc025d8748e1d8" dependencies = [ "atoi_simd", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index 82233590..b07bdf2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ authors = [ anyhow = "1.0" argon2 = "0.6" base64 = "0.23" -calamine = "0.37" +calamine = "0.38" chrono = { version = "0.4.44", features = ["serde"] } csv = "1.4" diesel = { version = "2.3.6", features = ["postgres", "chrono", "r2d2", "serde_json"] } From f79c7777e7c5260e0b122f7340124604e6bf5e21 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 3 Nov 2026 15:34:29 +0100 Subject: [PATCH 04/18] fix(contracts): make tooling reproducible and tighten transport discovery --- .github/workflows/marreq-ci.yml | 3 + .gitignore | 6 +- mcp-server/dist/gap-tools.js | 276 ------------------ mcp-server/dist/generated-contract.js | 1 - mcp-server/dist/pagination.js | 49 ---- mcp-server/dist/prompts.js | 52 ---- mcp-server/dist/resources.js | 49 ---- package-lock.json | 2 +- package.json | 1 + .../__pycache__/check.cpython-314.pyc | Bin 8966 -> 0 bytes .../check_baseline.cpython-314.pyc | Bin 2385 -> 0 bytes .../__pycache__/compatibility.cpython-314.pyc | Bin 23840 -> 0 bytes .../__pycache__/generate.cpython-314.pyc | Bin 6439 -> 0 bytes .../__pycache__/inventory.cpython-314.pyc | Bin 2736 -> 0 bytes .../test_compatibility.cpython-314.pyc | Bin 24708 -> 0 bytes .../test_contract_tooling.cpython-314.pyc | Bin 7690 -> 0 bytes .../verify_transcript.cpython-314.pyc | Bin 8050 -> 0 bytes scripts/generate-api-types.mjs | 74 ++++- tools/marreq-contracts/src/contract.rs | 10 +- tools/marreq-contracts/src/coverage.rs | 20 +- tools/marreq-contracts/tests/compatibility.rs | 63 ++++ 21 files changed, 164 insertions(+), 442 deletions(-) delete mode 100644 mcp-server/dist/gap-tools.js delete mode 100644 mcp-server/dist/generated-contract.js delete mode 100644 mcp-server/dist/pagination.js delete mode 100644 mcp-server/dist/prompts.js delete mode 100644 mcp-server/dist/resources.js delete mode 100644 scripts/contracts/__pycache__/check.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/check_baseline.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/compatibility.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/generate.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/inventory.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/test_compatibility.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc delete mode 100644 scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index ffa10987..01715c4e 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -323,6 +323,9 @@ jobs: with: tool: cargo-llvm-cov + - name: Install pinned OpenAPI standards engine for workspace tests + run: scripts/install-oasdiff.sh + - name: Install diesel CLI run: cargo install diesel_cli --no-default-features --features postgres diff --git a/.gitignore b/.gitignore index 7c21b673..220456b8 100644 --- a/.gitignore +++ b/.gitignore @@ -63,9 +63,11 @@ backups/ # nodejs node_modules frontend/dist +mcp-server/dist/ -# MCP server: do not ship compiled Vitest sources -mcp-server/dist/*.test.js +# Python bytecode +__pycache__/ +*.py[cod] # Export files exports/ diff --git a/mcp-server/dist/gap-tools.js b/mcp-server/dist/gap-tools.js deleted file mode 100644 index 7ca6712a..00000000 --- a/mcp-server/dist/gap-tools.js +++ /dev/null @@ -1,276 +0,0 @@ -/** - * MCP tools added for issue #416 (tool gaps). Each one wraps one project-scoped - * backend route; none is a generic REST passthrough. Modes follow the PDR: - * core = every mode, RX = read_extended and up, DW = draft_write, TW = the - * MARREQ_TRACE_WRITE flag (same gate as put_verification_matrix). - */ -import { randomUUID } from "node:crypto"; -import { z } from "zod"; -import { contextAllowsReadExtended } from "./context.js"; -import { paginate, paginationFields } from "./pagination.js"; -const id = (what) => z.number().int().positive().describe(what); -export function registerGapTools(deps) { - const { server, ctx, client, forProject, projectField, operationKey, withAudit, jsonContent } = deps; - const add = (spec) => { - const shape = { - ...(spec.global ? {} : projectField), - ...spec.shape, - ...(spec.paged ? paginationFields : {}), - }; - server.registerTool(spec.name, { - description: spec.description, - inputSchema: z.object(shape), - ...(spec.destructive ? { annotations: { destructiveHint: true } } : {}), - }, async (args) => { - const toolClient = spec.global ? client : forProject(args?.project_id); - const { idempotency_key: _key, ...audited } = args ?? {}; - const summary = spec.summary ? spec.summary(args ?? {}) : audited; - const out = await withAudit(toolClient, spec.name, JSON.stringify(summary), Boolean(spec.write), () => spec.run(toolClient, args ?? {})); - return { content: [jsonContent(spec.paged ? paginate(out, args ?? {}, 50) : out)] }; - }); - }; - // ----- Core: every mode ----- - add({ - name: "list_linked_use_cases", - description: "Use cases linked to a requirement or to a verification, with the link ids and suspect flags. Give exactly one of requirement_id or verification_id.", - shape: { - requirement_id: id("Requirement id").optional(), - verification_id: id("Verification id").optional(), - }, - paged: true, - run: (c, a) => { - if ((a.requirement_id == null) === (a.verification_id == null)) { - throw new Error("Give exactly one of requirement_id or verification_id"); - } - return a.requirement_id != null - ? c.listLinkedUseCases("requirement", a.requirement_id) - : c.listLinkedUseCases("verification", a.verification_id); - }, - }); - add({ - name: "ask_requirements", - description: "Answer a question from the project's requirements (retrieval plus an LLM call, so it costs time and money; prefer semantic_search_requirements when you only need matches). Fails with a clear error when answer generation is disabled on the server.", - shape: { - query: z.string().min(1), - k: z.number().int().min(1).max(20).optional().describe("Requirements used as context (default 10, max 20)."), - status_filter: id("Only requirements with this status id").optional(), - category_filter: id("Only requirements in this category id").optional(), - applicability_filter: id("Only requirements with this applicability id").optional(), - verification_filter: id("Only requirements with this verification method id").optional(), - }, - summary: (a) => ({ query_length: String(a.query ?? "").length, k: a.k }), - run: (c, a) => { - const { project_id: _p, ...payload } = a; - return c.askRequirements(payload); - }, - }); - add({ - name: "list_use_case_versions", - description: "Version history of a use case: version ids and numbers, approval states and change notes. Read a version with get_use_case(version_id).", - shape: { use_case_id: id("Use case id") }, - paged: true, - run: (c, a) => c.listUseCaseVersions(a.use_case_id), - }); - add({ - name: "get_baseline_contents", - description: "What a baseline recorded besides requirements: its use cases (kind=use_cases) or its verifications (kind=verifications).", - shape: { baseline_id: id("Baseline id"), kind: z.enum(["use_cases", "verifications"]) }, - paged: true, - run: (c, a) => c.getBaselineContents(a.baseline_id, a.kind), - }); - // ----- RX: read_extended and up ----- - if (contextAllowsReadExtended(ctx)) { - add({ - name: "list_requirement_link_types", - description: "Link types allowed between requirement versions (for link_requirements).", - shape: {}, - run: (c) => c.listRequirementLinkTypes(), - }); - add({ - name: "list_requirement_links", - description: "Requirement version links of the project, optionally filtered by source version, target version or link type. Use the link id with unlink_requirements.", - shape: { - source_version_id: id("Source requirement version id").optional(), - target_version_id: id("Target requirement version id").optional(), - link_type: z.string().min(1).optional(), - }, - paged: true, - run: (c, a) => c.listRequirementLinks({ - source_version_id: a.source_version_id, - target_version_id: a.target_version_id, - link_type: a.link_type, - }), - }); - add({ - name: "list_reviewers", - description: "User ids designated as reviewers of the project.", - shape: {}, - run: (c) => c.listReviewers(), - }); - add({ - name: "get_my_permissions", - description: "The authenticated user's effective permissions in the project. Check it before set_approval or other writes that may be refused.", - shape: {}, - run: (c) => c.getMyPermissions(), - }); - add({ - name: "list_saved_views", - description: "Saved requirement views visible to the user. Pass a view id as view_id to list_requirements.", - shape: {}, - paged: true, - run: (c) => c.listSavedViews(), - }); - add({ - name: "list_attachments", - description: "Attachments of one requirement or verification (metadata only; files are not downloaded). Over OAuth it needs both requirements:read and verifications:read.", - shape: { entity_type: z.enum(["requirement", "verification"]), entity_id: id("Requirement or verification id") }, - paged: true, - run: (c, a) => c.listAttachments(a.entity_type, a.entity_id), - }); - add({ - name: "get_attention", - description: "What needs the user's attention across their projects: approvals waiting, drafts to review, suspect links, notifications. Not tied to one project.", - shape: {}, - global: true, - run: (c) => c.getAttention(), - }); - add({ - name: "list_use_case_comments", - description: "Comments on a use case, optionally for one version or one step/condition (element_id).", - shape: { - use_case_id: id("Use case id"), - version_id: id("Only comments on this version").optional(), - element_id: z.string().min(1).optional(), - }, - paged: true, - run: (c, a) => c.listUseCaseComments(a.use_case_id, { version_id: a.version_id, element_id: a.element_id }), - }); - add({ - name: "get_close_out", - description: "Close-out status of the project, or of one requirement when requirement_id is given: what lacks a passing verification.", - shape: { requirement_id: id("Requirement id").optional() }, - run: (c, a) => c.getCloseOut(a.requirement_id), - }); - add({ - name: "impacted_tests", - description: "Verifications linked to a requirement that are marked suspect because the requirement changed. Run it after patch_requirement.", - shape: { requirement_id: id("Requirement id") }, - paged: true, - run: (c, a) => c.getImpactedTests(a.requirement_id), - }); - add({ - name: "list_verification_snapshots", - description: "Chronological snapshots of a verification, rebuilt from the audit log. Compare two with compare_verification_snapshots.", - shape: { verification_id: id("Verification id") }, - paged: true, - run: (c, a) => c.listVerificationSnapshots(a.verification_id), - }); - add({ - name: "compare_verification_snapshots", - description: "Structured diff between two snapshots of a verification (v1 = older, v2 = newer snapshot id).", - shape: { verification_id: id("Verification id"), v1: id("Older snapshot id"), v2: id("Newer snapshot id") }, - run: (c, a) => c.compareVerificationSnapshots(a.verification_id, a.v1, a.v2), - }); - add({ - name: "diff_baseline_verification_vs_current", - description: "Diff of a verification as a baseline recorded it against the verification now.", - shape: { baseline_id: id("Baseline id"), verification_id: id("Verification id") }, - run: (c, a) => c.diffBaselineVerificationVsCurrent(a.baseline_id, a.verification_id), - }); - } - // ----- DW: draft_write ----- - if (ctx.mode === "draft_write") { - add({ - name: "unlink_use_case_element", - description: "Delete one use case link (a requirement or verification linked to the use case or one of its steps). Get link ids from list_use_case_links. Deletes only the link, never the use case, requirement or verification.", - shape: { use_case_id: id("Use case id"), link_id: id("Link id") }, - write: true, - destructive: true, - run: (c, a) => c.deleteUseCaseLink(a.use_case_id, a.link_id), - }); - add({ - name: "clear_use_case_link_suspect", - description: "Clear the suspect flag of a use case link after checking the linked requirement still fits the use case.", - shape: { use_case_id: id("Use case id"), link_id: id("Link id") }, - write: true, - run: (c, a) => c.clearUseCaseLinkSuspect(a.use_case_id, a.link_id), - }); - add({ - name: "create_use_case_comment", - description: "Comment on a use case, on one version (use_case_version_id) or one step/condition (element_id). @username mentions of project members notify them. Retrying with the same idempotency_key does not post twice.", - shape: { - idempotency_key: operationKey, - use_case_id: id("Use case id"), - body: z.string().min(1), - use_case_version_id: id("Version the comment is about").optional(), - element_id: z.string().min(1).optional(), - }, - write: true, - summary: (a) => ({ - use_case_id: a.use_case_id, - body_len: String(a.body ?? "").length, - use_case_version_id: a.use_case_version_id, - element_id: a.element_id, - }), - run: (c, a) => c.createUseCaseComment(a.use_case_id, { body: a.body, use_case_version_id: a.use_case_version_id ?? null, element_id: a.element_id ?? null }, a.idempotency_key ?? randomUUID()), - }); - add({ - name: "discard_use_case_draft", - description: "Discard the shared draft of a use case (unpublished edits are lost; published versions stay). Read the draft first with get_use_case_diff to be sure it is stale.", - shape: { use_case_id: id("Use case id") }, - write: true, - destructive: true, - run: (c, a) => c.discardUseCaseDraft(a.use_case_id), - }); - add({ - name: "set_verification_field", - description: "Change one field of a verification without resending the rest. Fields: name, description, source, status_id, reference_code, parent_id, verification_method_id, author_id, reviewer_id. Status changes keep reviewer enforcement.", - shape: { - verification_id: id("Verification id"), - field: z.enum([ - "name", - "description", - "source", - "status_id", - "reference_code", - "parent_id", - "verification_method_id", - "author_id", - "reviewer_id", - ]), - value: z.string().describe("New value; ids as digits. parent_id: empty or 0 clears it."), - }, - write: true, - run: (c, a) => c.setVerificationField(a.verification_id, a.field, a.value), - }); - } - // ----- TW: MARREQ_TRACE_WRITE ----- - if (ctx.traceWrite) { - add({ - name: "link_requirements", - description: "Link two requirement versions (source derives from / refines / satisfies / depends on / relates to target; see list_requirement_link_types). Both versions must be in the project; cycles are refused. Retrying with the same idempotency_key returns the same link.", - shape: { - idempotency_key: operationKey, - source_version_id: id("Source requirement version id"), - target_version_id: id("Target requirement version id"), - link_type: z.string().min(1), - rationale: z.string().optional(), - }, - write: true, - run: (c, a) => c.createRequirementLink({ - source_version_id: a.source_version_id, - target_version_id: a.target_version_id, - link_type: a.link_type, - rationale: a.rationale ?? null, - }, a.idempotency_key ?? randomUUID()), - }); - add({ - name: "unlink_requirements", - description: "Delete one requirement version link by id (from list_requirement_links). Deletes only the link; baselines taken earlier keep what they recorded.", - shape: { link_id: id("Link id") }, - write: true, - destructive: true, - run: (c, a) => c.deleteRequirementLink(a.link_id), - }); - } -} diff --git a/mcp-server/dist/generated-contract.js b/mcp-server/dist/generated-contract.js deleted file mode 100644 index cb0ff5c3..00000000 --- a/mcp-server/dist/generated-contract.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/mcp-server/dist/pagination.js b/mcp-server/dist/pagination.js deleted file mode 100644 index 3cf3ef59..00000000 --- a/mcp-server/dist/pagination.js +++ /dev/null @@ -1,49 +0,0 @@ -import { z } from "zod"; -export const MAX_PAGE_SIZE = 200; -export const DEFAULT_PAGE_SIZE = 50; -/** Optional `limit` and opaque `cursor` inputs shared by list tools. */ -export const paginationFields = { - limit: z - .number() - .int() - .min(1) - .max(MAX_PAGE_SIZE) - .optional() - .describe(`Page size (max ${MAX_PAGE_SIZE}). Pass next_cursor from the previous page as cursor to continue.`), - cursor: z.string().optional().describe("Opaque cursor from a previous page's next_cursor."), -}; -function encodeCursor(offset) { - return Buffer.from(JSON.stringify({ o: offset }), "utf8").toString("base64url"); -} -function decodeCursor(cursor) { - try { - const { o } = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")); - if (typeof o === "number" && Number.isInteger(o) && o >= 0) - return o; - } - catch { - // fall through to the error below - } - throw new Error("Invalid cursor: pass the next_cursor value of a previous page unchanged"); -} -/** - * Slices a list response in the MCP server (the backend still returns the whole - * list). Tools that predate paging keep their full output until the caller sends - * `limit` or `cursor`; new list tools pass `defaultLimit` to page from the start. - * Anything that is not an array is returned as is. - */ -export function paginate(data, args, defaultLimit) { - if (!Array.isArray(data)) - return data; - if (args.limit == null && args.cursor == null && defaultLimit == null) - return data; - const limit = Math.min(args.limit ?? defaultLimit ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE); - const offset = args.cursor != null ? decodeCursor(args.cursor) : 0; - const end = offset + limit; - const page = { - items: data.slice(offset, end), - total: data.length, - next_cursor: end < data.length ? encodeCursor(end) : null, - }; - return page; -} diff --git a/mcp-server/dist/prompts.js b/mcp-server/dist/prompts.js deleted file mode 100644 index 8b6752a0..00000000 --- a/mcp-server/dist/prompts.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * MCP prompts (issue #416). Each prompt names the tools it expects, so a client - * can plan the calls. They only describe work; no prompt writes anything. - */ -import { z } from "zod"; -const projectArg = z.string().regex(/^\d+$/).describe("Project id from list_projects"); -const requirementArg = z.string().regex(/^\d+$/).describe("Requirement id"); -function user(text) { - return { messages: [{ role: "user", content: { type: "text", text } }] }; -} -export const PROMPT_NAMES = [ - "review_requirement", - "derive_children", - "coverage_gap_analysis", - "use_case_from_requirement", -]; -export function registerPrompts(server) { - server.registerPrompt("review_requirement", { - title: "Review a requirement", - description: "Check one requirement for ambiguity, testability, duplicates, traceability and suspect links.", - argsSchema: { project_id: projectArg, requirement_id: requirementArg }, - }, ({ project_id, requirement_id }) => user(`Review requirement ${requirement_id} in project ${project_id}; do not change anything.\n` + - `Tools: get_requirement, get_versions, trace_up, trace_down, get_close_out (with requirement_id), ` + - `list_linked_use_cases, semantic_search_requirements.\n` + - `Report: is it atomic, unambiguous and testable; does a similar requirement already exist; does it have a parent ` + - `and a passing verification; are any links suspect. Give findings first, then proposed edits for me to approve.`)); - server.registerPrompt("derive_children", { - title: "Derive child requirements", - description: "Propose atomic child requirements for a parent, without creating them.", - argsSchema: { project_id: projectArg, requirement_id: requirementArg }, - }, ({ project_id, requirement_id }) => user(`Propose child requirements for requirement ${requirement_id} in project ${project_id}.\n` + - `Tools: get_requirement, list_project_catalog, semantic_search_requirements, list_requirement_link_types.\n` + - `Write each child as one atomic, testable statement, check it against existing requirements for overlap, ` + - `and say which link type ties it to the parent. List them for my approval; create them with ` + - `create_requirement (parent_links) only after I say so.`)); - server.registerPrompt("coverage_gap_analysis", { - title: "Coverage gap analysis", - description: "Find requirements without verification and tests made stale by changes.", - argsSchema: { project_id: projectArg }, - }, ({ project_id }) => user(`Analyse verification coverage in project ${project_id}.\n` + - `Tools: coverage_report, get_close_out, list_requirements (has_tests=false), impacted_tests, get_verification_matrix.\n` + - `Rank the gaps by approval state and importance, list suspect tests, and suggest which verification would close ` + - `each gap. Do not link or create anything until I choose.`)); - server.registerPrompt("use_case_from_requirement", { - title: "Use case from a requirement", - description: "Draft a use case that exercises a requirement, and link it.", - argsSchema: { project_id: projectArg, requirement_id: requirementArg }, - }, ({ project_id, requirement_id }) => user(`Draft a use case for requirement ${requirement_id} in project ${project_id}.\n` + - `Tools: get_requirement, list_use_cases, create_use_case, edit_use_case_draft, link_use_case_element.\n` + - `Check list_use_cases for an existing one first. Outline the actor, preconditions, main steps and alternatives, ` + - `show me the outline, then create the draft and link it to the requirement. Publish only if I ask.`)); -} diff --git a/mcp-server/dist/resources.js b/mcp-server/dist/resources.js deleted file mode 100644 index 7b41c7fd..00000000 --- a/mcp-server/dist/resources.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * MCP resources (issue #416): requirements, use cases and the catalog as - * addressable documents, and the workflow guide. Each read reuses the client - * method and backend scope of the matching tool. - */ -import { readFile } from "node:fs/promises"; -import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; -const JSON_TYPE = "application/json"; -function one(value) { - return Array.isArray(value) ? value[0] ?? "" : value ?? ""; -} -function positiveInt(raw, what) { - const n = Number(raw); - if (!Number.isInteger(n) || n <= 0) - throw new Error(`Invalid ${what} in resource URI: ${raw}`); - return n; -} -async function guideText() { - try { - return await readFile(new URL("../MARREQ_SKILL.md", import.meta.url), "utf8"); - } - catch { - return "The Marreq workflow guide (MARREQ_SKILL.md) is not available in this deployment."; - } -} -export function registerResources(server, { client, withAudit }) { - const json = (uri, data) => ({ - contents: [{ uri: uri.href, mimeType: JSON_TYPE, text: JSON.stringify(data, null, 2) }], - }); - // The project in the URI is passed through; the backend still checks that the - // caller has the scope and project permission. - const read = async (uri, vars, name, fn) => { - const projectClient = client.withProject(positiveInt(one(vars.project_id), "project id")); - const out = await withAudit(projectClient, `resource:${name}`, JSON.stringify({ uri: uri.href }), false, () => fn(projectClient)); - return json(uri, out); - }; - server.registerResource("requirement", new ResourceTemplate("marreq://projects/{project_id}/requirements/{id}", { list: undefined }), { title: "Requirement", description: "One requirement with its trace summary.", mimeType: JSON_TYPE }, (uri, vars) => read(uri, vars, "requirement", (c) => c.getRequirement(positiveInt(one(vars.id), "requirement id")))); - server.registerResource("use-case", new ResourceTemplate("marreq://projects/{project_id}/use-cases/{id}", { list: undefined }), { title: "Use case", description: "The current version of one use case.", mimeType: JSON_TYPE }, (uri, vars) => read(uri, vars, "use-case", (c) => c.getUseCase(positiveInt(one(vars.id), "use case id")))); - server.registerResource("catalog", new ResourceTemplate("marreq://projects/{project_id}/catalog", { list: undefined }), { - title: "Project catalog", - description: "Statuses, categories, applicability, verification methods and users of the project.", - mimeType: JSON_TYPE, - }, (uri, vars) => read(uri, vars, "catalog", (c) => c.listProjectCatalog())); - server.registerResource("guide", "marreq://guide", { - title: "Marreq workflow guide", - description: "How to review, create and modify requirements and use cases with the Marreq tools.", - mimeType: "text/markdown", - }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text: await guideText() }] })); -} diff --git a/package-lock.json b/package-lock.json index 2a74b6aa..a32db7ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "stylelint": "^17.16.0", "stylelint-config-standard": "^40.0.0", "stylelint-order": "^7.0.1", + "typescript": "5.9.3", "vitest": "^5.0.2" }, "engines": { @@ -4590,7 +4591,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 753985ad..430c99ed 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "stylelint": "^17.16.0", "stylelint-config-standard": "^40.0.0", "stylelint-order": "^7.0.1", + "typescript": "5.9.3", "vitest": "^5.0.2" } } diff --git a/scripts/contracts/__pycache__/check.cpython-314.pyc b/scripts/contracts/__pycache__/check.cpython-314.pyc deleted file mode 100644 index 2df299a0033b5b03166e6a2eedb2b039b0377e77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8966 zcmbtaeQX=Ym7iTMze$mlB~g-PX>HkZXv_NWw=Fx4EZLSU%VD=N?WRgtid;!+QRI4e zX-TwmQG)|2Cn@X%zKhc45C>PFz9L0cpaoQ*z{PNYxQ8NLh1AEb6{Lr|`=ftd68rAp z{BgK9`$dYDo%U`Z&hE^-c{B6o?VI0wqn%}57lKs#r$1i2xB;Pm#RVf#`pbil;AI*y zNJ1|mhUg_ELVc1FsXi@|MSW5d1y8cq+GCS!4Vc!_YwvMLjvlAv>~Tr19=GJ~@kpK? zujEzRsot_4pX5{Dt-b!9fD~vzj4gm8jgD6Qz8yE+aD_*Q*oChx;l&D3h4!uZ) zn~iA}o}1B&6`Rpsy<#t+y#Pm|QT>J7*|U*Yl1s!SmVTaRQ=Q@7W?D#RpcqeIW`)=Y z+YE2xd`#koxFjb{(c`QX1NvAq9ZMx>fxXPJ6Ex%rHWg=E&V~rZaam^>vMChv{PP2f zw=12JgjifUAqZ(fp_1uXLb37bBp07j>@beVrBjMsV8wLuG7Bu6BWwyrm)Hapyka^d z#M!7iFEL(N9zqT%Sm5_yp2RycsIL&2YvvK-@S~Ia5aNjl3S-)m#1KI=XzVPC5Y6ra zLD*h+H{KZ1kfYxm#i(gOeFqS7qWU6^jO8!kc=839DH$Qdcx5GtaUO!25m-YV>Z?MC zp@2@+*0qF(Kngll2qB>Hu5HI9Lnn9(V~to;zQCytbWJa5qx3?56!tydKVnJ&);y@K z`3dp`tz}&6$!aiQ@Qoltf_`+4%#f`pL!RUiy0j$>-(YDn=DuBT(ANsz!C<993#$$q z?}cY!pN;X1=U}x#6$U<~X)~7MrqbGi6b&xnh9}buk$q#DoH8U&Ho^)U;K!333c0`b zjmHFmy|gQy7T8u%h{sJu28LKU_rUMLDr8$w11S0cIynU53521hG%?m9H35MzM1%;# z=TVx9)Z`i1W+H5U1O>{ce*ne?Bs68|dlDDMa&W>_$(JzIB$~E@;v_VIFjOPjXW@vD z?Q2VmBr48mRANO*j7GDL7W@X4*-FzSfRr?A0@Y_jq)-O)&01OnF+?dgF*7{OO)6GC zCIBZY6&q(22b)TW6Pz>}A{DZwO`(RlBr9T(G||ez*3tAh+X^-d>RKn#!kEa%;%uw7 zBduaw;CM-FHO7RuQ8qr-!cQsABX~-ayzs2DMK@8V9Xygu$74zH*%p%qE4%n3$o>TT z_XR?g7c2`+U9*XguiUvJM-mIC5;q5APJ)(~iB9sr@#7TvrTMrBrJDoL8YTyDYnc2F zZp9C^G^CY=NFa%m<631(>mrg9xGa)aF%fXu_`3`RpwYi+Ks8Wp-~AYC;GI9UYD4mKRxkN`|zDUdbM0vJOi1aXEx;~Myh zn<3LB9k^(Nh3?>Nw<>N{%u;V}d~4$awfsoLD*tM29TBn$9(c_L^zTq)1vr)P60?5e z(@dr_>Y}s$ViDlAXUoRp{H{VDLnkRv(8-V|TV=4&zi#NmgSuunmDUesxMU*ArH0eE zxwfX-k`S^CfvG~VB_Ni1@1MEH;%`+ z6lPrg*uX~1MpI;Kz(mVMF`fE)yU`7c#~_S8P6+ZUi23p6kYBNgtfbg7JjOGsvM5$z zB$*x({IIz~05ZiXu(1S~mq|&n#1eo)#Iyh~L$PyW6q^O$Aqk3GV3PoOF0)Z7t=LCn z;%Jf^0@~5o?tS|em&#*k3QUvY9AzgH+z1#cus&ARuqe(1C$MoTEldgcKvL}3c!2O# zb)zAv3aj`^jD~;_95!ZRGnoHrwCeG_eq#E>{mO|M=ZeF7-E++YdJJ~sC(>&0Mg2X+c$!#6t%vubM4e3^XnKcP;pL-HtE$ch68OZr}Be zYaO$#OYYF^u@5UguDw&c)Y7+7Rr_}8%~YX?fKT5P9o+_Zcy}C>Q<5_i#2cO5KbBwK+cf`uri_3e+h!wCC zSa*!8c^hwy5R9Xk;%OKW#q?4vLlaXh`>N!UY`@<->VU=FS! zOCLxT&!KOtJ{LFd3gom--i2G@+%|!?v^as|-dm_|&=#_CsJRY>W z7i4dHtlD@&n-|gHN}>k_ecDH)>=USz+#$u zAMqO_4mKK-GHi#c5dd|csEf5m-v(?2HW>X2PlJxU){=SUb~fdX_j9v zQ)<}`p8S^V#zZ&FyrBN|kmr22-G8q1|36qbC z=nvD`ZAEQr9X-y8B9|JWxs*YCn1(1_OyFWEfY$(m1pv%8&3#a;aqMPf1CzUS7_Sq8 zdsFZ#XuT5i9*=>W2m|}f9wV8UkPsD{_RKcKGSVnamWu-fPp4?Ly11v`>^TZC$RsOZ zh_1e5%k`IH@>(-;KtoV~8-_R}oe;QT2`16lK?|7_{kWHJ^Z`fz>z0x|h5a;R5W6%G zze@{*st!%|9)wY8t)7m z#t6u^skm3m7Ykli4hj>JPlQ;v_!{fF*77e zoP=Y%!)XDQKFW^AXn5m5KSm(vie;{j*^=F0N;;DQ_2h;**m;7^)|&Okia;Rae3F&e zfh@xd=|m>Z3Wepu;1DQ*a4#n%r(ht1xPu<%Qk*!77e|NN=$I;wAr>aa(tKtJdZ_bR z1LwiOYe5CZ>TqVq8HF1 zP;BfZ2k|ojha&|*t4MV%6>A0v1>A}QIH7XI4>89K=$`6q=7VSK_T8l9;NC_5-kf{iboVOdp6Sk0!BxsL zGw|1E?^m|YI9DBIZ%8-0XAdn_Y?ZfnELL>nd`FfXN3na{_HPx{?^eik?_y1FuHy7` z|Ek?Jb2MkKnH|6VqP(diZ}0f+#D_1-=Y|$f#dF6J`H_^Iz6vhqUlD!ee_};DLxOiZ zO7xQtA(94<)v}Fp-RWFepX}&c@%pAuuI_p6SI2VRu3sLz9lKHW!>QTLvps*eQ!Z<| zO?+Aw`sK0dld`vKrMYb``1Ahd=3@)Z$MVgcOM$_K=Faznw*vR1KV*NG{p9K$_QTkX z_8U#JTi)97yQ^*a>sIrpBq$-Mj6il_W~|FwR(?$CVWe5G7_Ebn=44P{%--98uj^>;t4Ufg#&?>=pI z=$P-C-zV30<~_&PbZF1Hch3#~dhEl{;{G#B?lWeGmfPZOR<7N<OB?^tf{Txjpiw|6bN*@gD5_Yc3n|5oSi-5+1Rb6GwcUp|${ zd+KK{U7xr%@%q%v*lb|7eYP>@sRz>O?oZw9=l0-i_j^6>^vK&fb9Kk>ZOzr4HB_ML zV%{+Z1n+h0H4E4_RU{kmUB-Xn%Gk{^@xQfo<=+F{GBg|+D z#M{j{htUqVPJZJ(j7?cdf>oQb0b>p?k&NR2w(yV`CqBlERKT)>5DCx{%-6h~GU{MG zy8KiG-&pX#zG*g$D?%D;-GLbQpn0!V6f5#X$W}}7ERE4%7tG$;wa-g%xV{8Y`j8i{ zYTXG|gHnJYvQ_Kj6U8M$XcQu=LWa#AK2d-LMTo^fPbwY&h^E~#!~Jk3DPd@u;D(3c zEVTiY!#Bs&D4XQ7<#;^xJTt*aww+;(x*6l3+K$f>}?KMXk+qgL2OmXvtGo!MrTH z>lWN~Id?snM7w+0PQ#yUr&pa`+5g<4^SNcmnFYt0-w*uZg@1Y>e_-!I7% zR~GxPTd77N%=cb zx$=85cQNl8%TvkE{Z%vVGPP;86EZldK|YRX6t6ZK4G||pK>?dFA%-*T#|opE#3BNk zs?t5SC`Lk2{a+cbDAorVz{FH!yAC- zhc7EZO(;Nbl7Mf_gf!0Z?Uv#=0yjUIB>Su|2}S(vi{FCm3kyLI|AGeYBj0^gb|1O# zBiFy9ou8xrWz_!}s{af%er59z-Q>giMxy$wGlY|<|DqZJPRiyjH7k_;2R*O$TpM^j OvP4zDkx$jCL;f$UI>}-H diff --git a/scripts/contracts/__pycache__/check_baseline.cpython-314.pyc b/scripts/contracts/__pycache__/check_baseline.cpython-314.pyc deleted file mode 100644 index 74006e60254830bd3c99e11b53c67a4805024417..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2385 zcmahKTWl29^~~%%cGuW7#*()V#S?F1x-RyXVhd<33IU8^!EDBOA;M-g-nn=OW@pCt z&Ki5AN>=(&nx7<6KTJeb=MVn)3{s_%^tT^tb_rxiq*PTD_0OsM@?X#0-LXx6a40`C4;%YdUj6C^aI4;0F)!1>`8b&(>lxi>_t3rO4XwSsF>s3 zdHx;?(X*C?dqb~skNr-ZK!f-dKjw3x&g_q9B=n)_`pTjvP(*n-raO7GH8$)J=h426}3o04Y} zp9Jwnf%(>)O(uiboa_1wYtg$BD1=g%vOvL-X0B7oXDL~<+%nTHd&D`FJ+FC`n1+u< zzDTHb)4~!D)nkD#632Jx@-H>V)ruCwy;U%5tCW6ri%k(k4Qj2A^# z*|%+M8I?2@7h02{5k&>z5&UmXA{Z0yr!jZ6C`L7q3$|M}Q%WEeTzofb65=&>GR~?J z3nKHxM`oAe$zLqPpTTtP|@&F4#o<>d2~*G#v- z(k59XHva^r5jcit4KEud8^1S-<(Sy3jkKlq25LT!{P{fX;rWtoVe1Df&$MmLQ5@)i z)v(Nh_E#F2@-A!i4dw07$S=3RF-jz#4?Hc#QOuQiRWGY^{)Qkz4bu&Od9-F1*w-FMl=2oQ4>3Q diff --git a/scripts/contracts/__pycache__/compatibility.cpython-314.pyc b/scripts/contracts/__pycache__/compatibility.cpython-314.pyc deleted file mode 100644 index 57d09d00c3473d3f7b0b34edc75ca5dca0cda550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23840 zcmb_^YjhjOm1Z~I1V93Ofo~EN9}+1(^nOqhB~cVbiF(i=1?(ioe_2HIYuU)V|wFRGbf%6eKxzHXEP_7o!Q+c8x%OOW;b&(^CM^Xhb?(` z#@RpnRW-WNpdiWFY)foZb-k-@Ro(jTy|pzj*T~>0|IM$iynK*heoZ{mqZJ+OqYEs< z%rFin!dzw?>`As)6;ag?x9X%iqNcx^h=%@ZBU<{ai|FvHKB@0DL=368IguP1W{eo= zuPI`}ujZt=H#d@3!(1}dGM6m7P!@UmYsFvXdsoWSze)L3bYE7rl&8N&3brwh!YMXV zSj^Ni4s8kJ&|NCpr50mEiXHkACSr3K@Gf!W;9cq{au`b(KO3<+Oz@XE%y^eOa`CQk z+TFpakSpRF^Z9*|n|Al4*ZpGHJ|5)kzNx9{h-=I* zMhOPIfrx!h@*Uoueo&#`2 zeB&;6#O?`R5BP&Fk1sIM?wXEFqFkRFRSyR2V_ttSFcG##g7#x4+mCg(8JpOo$rT6$ zBQ!$zEc}{&S7Z{uhHEar&*O@C-BRa~-xNKtk8JSFFqfDJ3rbX7E{&)iY(zu)u}PiG zJr)c^IMn7i#|63fnX^sWq+xV4;F|J|jwX$xqfLib}PX?#F?Wildqy2i2dohge^R`d9 z(4SY^!*0$OiiF$UsrnF?Vj$Z>H#r@$p|4Un!eo|xi}_Sz;Buw!?o^xMXBbj4=$3u- zZ{V0=M&vLjBT-X-impj?>5(Yz06WS~tESbXG7W~<5seh9pVVc;Q_DD6Dd%bRw90{* z?_i|4Q^V9Tc4jNHIhATcMQGoL^`MHWq5m9+aX?+g_}KT^vrQU~R4%CrhP;8K=B8`P zpUern#=WD2VwzM*4TyX)sS8IyR8wJO#x&ZwJeq{W;K-;~;xD`nuNme+PVTJd)$-SB zZr3Do%HuiZ^WzUJg|FTE&aL@z!P4;0YQ|WB0Q?tn%6}U+A<>`Oj;ghvs7*(UaxyyB zfH2yz_i4_`jvXQOpwF(9OKPsCnpEdm9q3>%I2%$`8Wn z)R#UZgcw8@=?wZfnf`pqo&I&m>7^2-{K&+JZrQT!<>BqE`!9`nnwv)2p6rWm^4ri+v&(u_xKKrkteF7xo1ZC*bxb)fl*>it{FO#;5)M zDObcjNl81YjZBC92%mC=l4@TdlGKJnSjjNWwU|27-Y}u#MscL|sdb0jgm1O@3qOa~ z4D*GKG27<6Zx19)wX3Gu4+=i4{MVKI9vAPPOf>mcn|u%QD){On%f+AM@Rg^8yguI0 z_rPR*v+&J<`G$A4-rX8=3+2s1{#L=%!fRUS1j`@+G7l&TG@>?{bL%N2P(cSWwL3Co zs7YJ!V4kqsR4sHz6~D-2W2R}yT&7QY&?d2(Ihp;)!Lu=rTFMuMA(M?0r6_kgDNcwb z1hpTzdkQlN4Jp`cDbBDCO-SWbc?(GVp@MyHW~qZ#%72m_D%eTZT3xaT>)f=D!@4+z zeZw2@j86Fi*}l4NL>qhox~gy3&ZfK(7gl-~HWjbm6OQi6SS0Oouv8MZoX3ki8*SK- z%op%YO;6dqH{4#YCyXuZ2Jts(xia)iQseQtBcNDsL|lRiwKb_ZLU2j7KX^TXiqIdx=_&XbKm5eNnHGezl#@^AyYBvaxh`qv})S)u6v>3hgbMrr})!j{7!eG z(H(De6Vj|aCggSVhVFHJRqT9R-%P(bguDUXFz~>bKi9yQwyhPk3&w3T$JTfE-J6c@ z9{ld!x!rtO`&!|)xM@3o@$w8(n^yHD^T*ykm64|@Z+1VgDH4gA)LD86`N6M!^p9A9 zzA{l)5h8FhR^~jG{5ED<)nj3pka|QW^P%ldm9z<*RrO&d%3-PzFQl%^*px=(Ek#<7 zq)9M=T9Goq_(-Js58{_h0u5=Lntqw!rDkDz$t#_dqX!cTq}xTw9`(h_EU%yrwNsP1 zx(>1-tyAOBC?`X|yv{1hk%`{Q^lU{h=!TsA$bIAE&^feH8?%P%9R?{}S!PJv*DChj z2=)Z=>Bl78_+TTJYZB59YWs^f#vqGNf63!trOh9ssXMQqR=1P!L|NBC5!{4R=V13; zXBcrj`mnzYO^m7fPy;%m>X1I9lcs$Qqo9E2+8C$m9P8AaWq0c&TB!S7>VPt&IwwpFczmk*vnl5+jq_Dzu6Ym)FX0KRo5PEoeqSjL!lr?_C+}> zu*d045Y@|hu^D#QJAQ)>X3m~Xvh7jTt?l^RmSk@=nN#bJ-xrP~^;kHAe(ZRSh?+EF zALSz9>%NG%Z*q3>6>-40V|&%OgQ1&AgU9O)QI~EyL>T0x))(MU?7ir{Nte1X;d>ezvBKe@`!7dWEBe|EM|dChq8a8N z_4#x4Z)+0zs#Sf}-*t2fmct*{FV?@QpVusC?ps?wu3xbn<_(9*O6YmJTil`7ErqkD zb$#w!(Km0c=M~KD`9uG#dfig++O6BS<~<2Z-KwQ77Jm1o2L(0!rgMD%z`beSeqJbW z@@D6He%Y*Uz0fwteShzrL$4o-72Ypwm_4>$T=7od-M$6Qf?KHEk*Mrgsq9$VwOlAv z9$zc&`A*O0YNn|1AM=avRDG{%{+f{Ah)fK5ujSs(UF`npsUM&E=ycrX`0?oioDaJ#W(l#umhzb9M6oypdqUZ!`=}_-bwXqwPXF{R$k(MTTF-6_6fMWE; z$H_jK2TGMk%Fu{q;JjTn#fH@QEyu4WJ-yTXk>ZpGZq>>OH$F2&QLJ%n{|=#(328w(ybU!voX z(W!T6DINnl09#O^jC;nA>2G6$`A(x3M?yOyyJP zt&q^$4*4_yYI`?DZI$RxpmvY<=TL~8C?;@tMnCA4{gBxU`>-LW>VWO&DRn4%tPY*2 zI#m7Mb$IjltwWmflq*cO+3qm3$p>?3EEH+7Q(=}g%9TmV2mJsX5bMyB=T0$QIcym# z2fz(7N3*Y94t={^-;gQ$3YHPa{Dgdr4pyEgxlhPP87D7mpPHn$D*DVUeN(RF(l_+U zS=uM2Y@;#sid-j))0{QNEjfnOX~9m%rl=JRH2OIUQ$8X?BuzF^O{a30R z=25@c_k&q;a^@;`+B13SoQM7!&9?;5&0~2wa>NyD0{eO;10PEdCr60ynKjN%e>v%~ zd&0;mV~`!HG+WJ_I-K6CqmTNzAXs050^hWUgVUTFxGzCuC`{~jdqafP9lL248Akxp zoP8VswOs_L0Wmvp7WalD;SMmteynmuo|caEP$j zNb*=HN%B}eZN~EozS}+N3QVL*0mut92`oDWk?k(G+vfq}3pDno9hCxbDgNMBZw5p~PSXH%*hkmk zm?6uA%%I^z>TxjXWexCdiYOVy*$vFx6VK%-hEZlw2vgan@VyU+K5{eUZPFywI7)D2 z^P(pjt{pC5xH_@J#N!3wFC_?9OOl0?#o_5Ope}6(sURWT*+xH)AYynYUNg+P)pp1D zx^aF_Y=>ZNoYg+C6uf;ZHvNGoQMq%qa_8J`!E%&099?QyDiK|eLwTgl$c%k-1&)L4 zCNN7g%plXVg<-Dd52(m?cz~6*T@R3V%1!}%+t-^Bf?gsa4i;&Ula!*Fxxvh6v1EWX z6j`b|X1ChMLiC^qJ$fg+Tv7)*8S`>ysjvV6sy3YdB1v{E1u4ylXlKsB32y)-z;!0e zHg=30^a9Eb@51X}euH-Khxzl)*wMQkiHd%{qJQD);>q`KCECyN?dO)a+;e@> z8gK58SM&=PhrbLH?(rLzo$MRtoouXdCmYpw*h!z;qeWzvL;8}M1>#IUn?Z4MPl6Lv z_9U1psV7r?0j7-h1&$%4FF1l`q#Dm**=M*8ypjfS!h!Hf&W(1l8#n^-at^%4@fXIa zC)Ly~=l{BSxqk7&{iZ{I)BI&vgMe@37qL-G2ZUA<-UoC#YDSxY*G15oIMWkZO0CzoOuIN2M#}4ns@j^Ps^LqW1Stbh?TY}Y)_c6;u4zBc=iVExDFR&^ zQVu%NT^=F{OXghXaI~gQBcyBq0dN}kBuyT#DEGna|E<1WoDRP=-)QBg!;w~qp1l4} zvR843$*YIFj*u5!H}ol(GN}iQO7P000bfp%13p~70CjO)h@Z?sz-ez_0uqxPgosWK zCk7^&i=gzf1FtWT_HxG%rAZ|ge}uR-+za^OJmfV-USt&Tb(FZ>a)$XVk1^!M zfy(ZQ4ZhX6;9TTB7+G>IPyf5obxZzi4uL?5zwx#C##r}TZR>>#^0X#~zFMP{mmKR5K^=n}1F{a}w*~+@4oodV3&d zduw>jvSs1qTHfv@{aW7PnG@@|mYJRhmg2dIKl&O#7gPRg9k)B?+^-&Y=+0 zG2L6OGu<;sXRiKtz2Q#no$wpsI9-f{@{v2?JK^u8y@`^M)sm4E_%dHQZ;WmJpm8z( zM@%*wLgAosqx=$U8?{>7b(4Se~&`{wgC$6~wrEoXS!+53ibfW55G zv%e}VyVLP{$Gm@`ODJrgIlZ3U&sXnS^azCq(7sF| zcInvCZociPP~LUleC+ca#(L;8BcnBa`^?wR%$paqLTT&kn?BmJ)WSdS;xCNxV;zI6UX>{`6CHP!q#$LF2zT)ca6LBCieRP9;P{oMGmae42(ZeibZ zyz?@Dev}_|@dIOm%^f#*K>0IgME*d987n;m8p7OVABpg0I|I?z22olz&Wdcok9zU9<c?9gyHa7XLzM4GN+p#pSuNC_K}_`X~A4AX~7gQ7f6H&LX;oEt5L%2 zf=el`8%m%2)oE0AD2;b%(u{r@ZG^x^&ONQiks-#}A(2%`n?A=6b;)0KKw9-qZ8p-8 zNZZOB*Y>=hnh>|01}fC~ku&QlUrK{>DdRK}Br9Yd`WBUVQ@tstJ}c5{Mtqi@vsD>* z32>*O$&5*qJMf2Yb>0I??yFyt^5DnyE`?Qs16E29Wy6g_=@_Xy(b4i$uo zodwQ9I$v?X2Bj5er4U(jQ<`C-R^(IY(&{WgFXw0UGFbjT9Fo3r8Q51)qe5qavPZvK z{U*@kPn|bLTE7Bi{m`1HV~$^?CWUEC_X+J(#vtop5vfU51anQKnh|n@p^vjrnhlC? zMbbAVZY__~v(}GdUVv#rmXT{S(ztYZ8l`+1*M`@YN z`*F4h5{FQwv)q}{#!!{BV#AkeXI0j|tWHyvv-(*vXj`APJ~7Tpj7g1>Ql3z@f(D;Z zwjxHz9;$KHJZ&VL_NRsZ4t2L@)cyA;Gjp_h04s({pE+afzvB$3`5ohv8Bmt^fxTZbJmTqp== z+CJqALtl3S=&3zwu%}cuqlNMTZaRQdn-7YZp6Ce(D^hyv_DSePrGuc!Kk2&W3vvfG z2rW`_m@!C{BV+KOiDmRIgj6}I!Mz}IdV9#GD#IwQZ+=$pCYFOJq~!Ur_tf45k?QG z^J%m^?M6G@NF+z(2~4pCg%}GiYds|fCUo-8hz0f^xTYAi-0t|_A&T3 z=AP7(?uPg(YE4I+@_KwO(wJM1e2jp8b}~@X=zV$b0mjc z5-ts7IGIZ#rM)nVAS0VJK+=H8C%Q!GYSKhfrpPC0BI#0|a4j;H6#Qg3d!r+zc~1o@ z7_dJ_n%npbzXd=G`pu;^F@sRLlh+hKD5;*mE|hG8uJHp+{@j+hrh@!#j%&&vSjzbF zt%7CuOz(rdQogK7$lI9$qSuQm@0@-8Y@(?CwgF&bN%?Iv!4PeC%&(i@$m1iFEvvns&AaZ{B9x;o~&dHbHl>x;gn8+>_h+N`|EQhx>FC1O9wIX2F$QM7izPICd zO;NfjQMzTNbj$tH=G(oqM`x$kE!KHs+_GuCu=LLU*Z0p~OcZWeE!?tRRDS2o>t|w) zSoeZuiCqjYHS?Q}-!GyHnoX!|QJFIOV)@e1zo<%V?^@a3wcL@|es*>HS!EWgh3yM< zeCr8*Q*XTJWU4FP9tUd9{@JBO!=9CfJwWIRgof^A?=O9y`1t+{{KXNW=MsUFU0&WB z;;(XiD9n#WgzD-0)@z?zn6j;3SQ)crHu~yeNKne(8cmp>;n|WgFo*c^&bayTdX@d1 zp}Rw|ON(7X)!wDTpO<`GvRuXYJB0lMb6aK&5)l|hA0WK=&d9Kn zFFzbNA6c(%e8+#+zp#ICOsL+Us6M;`Sw!{G+h=Ew&+T2e6wUX(bNcS-g))>^xi@av zs~CK~e%Ipp#S=^W`0DQa*5khtr5fmqq4$P=FuYj0v_ojZjtjAlub!B&^BZCSBx5kP5|60L_biQJ?Z{1QZ zN-P9RZEQHvuzRIpx6rVmGo?(A^VL0Z>xuQU`b1gN3RbSNtqU(E+B#R-I)%0)iLxVn z*^xPIn(l@di`N?VCK?W`G#ps!MQs~S-aEh#jjZ)uO7yu_`hWmm;r#)jFUUu(@!{*A zG0Y9NONF0fs*~iWOLdC;aBQFI5yRA+R{d{=$v>m|Ld}#PWxvR0N+5O{VwvKGFRrja zir=i88-D|KOVzxl`pbvMS;ppIzf2ZCM@9R;Y^c0YqPtg8d7)C5A-c!fEn>+x-~g74 zxA^ocMIotgwMz96*@o$9Ctiv3%%-U&o#E;jss;c_fgT*>q~A9N!N89%A)t{M zkB*Mrgjr=WXVQDaX-A zynUs-T`1o!n06#g2Uko71yd)l>3rNMMvufexw4POG$a>jDHjuMV%vWs9hv6gB-0mKTmCp|zPS~~?MjC!Dbsjv+qF{vKZBuB=Wq}XXums}R~ zNul+ohiIxO9ZPaiO}K2eF{DrJursS~fntgDz+^}JG!vKpGAU;I*Qt713aL$UowL8{H|pBkZQvi6i}_(!Zg+}$&&N-hPoznF`Cp!X;aXrihU^qJbC+dER108l%9`xF$9$hi>ePk}Jh3hgk< z7cT?wz;e?a334~1TjeC-n^Q1=e-TG`w~L(3vqVOfB_LrEW=ug2CjS2P9RSgq1F4$@ zh~^Jes$e&72Lu4aC?F6}*;KS?&#qUZWpdeQNh@{-k~=jYPU>*U0bJ1nQNY|rjy1%x zzwO%gWR46LkY2gY;c@}F*)$ckwx?)9G)FEXT4VS4!mbI;8wTXPC?t%>(QKCKR1PP~+$ZEksw8)5ip2-&;b&cU{icTQ{bQpYz z=i)pBK=+l}osC9<(25GIm*Hk>7vDU-+Lu$TLjR(t$%A6qT;LTc0p(UNvq0 zVCO>gM~9at<6C=RhdO_A{zbm&#NX=f-4J>&@F$+g&I+`$bt(v;z^u2uLNg?kPZ#V@b{<%~0*Vc*}10rWiXw`H`PCCd}o)_|*yuq1DLO2RJ^u#^Q@-_a9pRb$}@&ddepln3% zTGOF~sdLrT`HRMTMgOsdcZ~3tylal}c-Od`Lzis%`ydxqbgy278iGFe+tIH_=e7v? z3chA%T)%Vi+E1fDjxKK%_MYV(!|}bta`JSqd}`T3RFu-HTvz^tC~?7jF1A0mb>aEN z@S>MLaFMqU3k4&*d4y;vH9^ftYo6_y>sT{a$8@pintexHvy+_J8|TZ{3hLvU28E7H zYT3fbGYr|7D3kOv%mSB6sf>_jd6fFa&?+YLg+9_EDQ$--GjzQao>oQx^Hnk>ZU`t5 ztO?Q0p^rm0sluiU-CX6O4AUO6-Ow!rVHqavj#4UG*_fMb#R?<)GKghWDY0x5G+8&L zEro|NgojR5`tF5vtFtj*k|+ei+pQvS8&X57NS(>bQPxRju%wp4npxJc-q;$tX>V+c z4F68DP|wgRfMJcppzJ4U%f$w+$$DQzPFfHV(kr$~xdeyNVcMq3S_|3O18j`U4#Mop zn5_mGF-+NFaPvdA71s`==^$5X1?a$_$VZ*Y>Sk~GwCaFrUk4N;41~IzIm9iRRi0t7 zJ!iF}PO~-8{VoR+@;bu=F_r@vYb~Bdsh~NOD&+PF+Ur6k(ye8u|Fs~#EPaD5U zt%4>zHYKY4UF6hCbH16V>}<%xQS4Or%dBde{w4OA%**q+DI0YhacF2KSfCtALjlTuA~9?91*156Fs)^cBB*=MK4zZ z-_ZhV7^>0MBFRcx$R$ZMF*P5ba)+W-Dc&TVSF3_ei8e~NUZlfC=1XL4aCs0dA=;9{ zsndLvB0(yW%ovGmq)n;|;9kYOLP--8zDFU*A@?`IHGAPsI#weNw#X<`{x=Sjc!ycTDP_8qT$L6qIR`VtjCJN)*X%5NUjCK%v z8L42%Wstp;mB;J$L$m-@e^PJ;>_PURo#b#24K1a`0@lzF>^LxAV9`R3-YL5UP#L9M zHf6)1;=YT7lHCS1T}W!F4mySTAGcdbZlWTyo1;^%fNvc5McTAQw%|}FJ@6th zmff#LMP_#LT}{H&uxe`fpm5>BkE&KXrs7Rg;Jf%rmykEc8^*wQ&KcJ9Wqj4axc;C7 zstyO@M*=ijw~%+7Hyr;vXGF-GUU5e5dEOk3mHt`vV$nyDrS6|Z6MK7C_Vxe z5j=B(&zpwqV$QdgXNP1&pZD#TzW&nOegPN4lrA)_)$WSxchQVfw-<>@{I;t3iI{J# zdYfR|K67rpxbn{M>%)BQejvTYoik@2=#8_Tg1&tIm4zw3npBi#F(Bj(@`k|&xkYoA zV`tWicL=#VKN?!PeDBQKp^L)qVF+6EIp4fa!w`XYfus^Un^@_cz&&H zuaLj*zNvGm3$K*&HE%l|H=Oy@S_kn>Vfp;7?;n~qJSZxk-}lbJy9XC6Ld8CzX#edT z!lstTP4?K}YQyfO_IO9X1Q!p^_r#hPh8CY+Ydy@j9N~{#UVQ7LiKWTq z=vt?P9~|aK#@7acTVLh5m)3kQ$M?J<6yLgUxCN#C{QA$#Om5ly_}k4fA7JmMwe8)@ zSAeM=QJqlzGx<5A`ds|^g8ZCQ;b(yD7eAg;tps1a93Y+@|eaL>aQnjghrKiS%TvQTqxU(v}@s~hwtKZN0> zOmq~>(pSJ>5leg=%-$3;9wKIbGUF_T6PD4T8Ua(2im%>_a>xUwCVPCSf;&piu@3EC zOope%lv(8`<|VU-PmEzu)Ga5YLr69dk-ks^>iDQY&6?X0ErEc)Oyd!-?@Ke91#7J z#t*ZWBK6M&jl-gBN!B{cm?845m8$!Ue%Fq)OEE{bNv?jv4QzPGl}eYvJWA9l)r=eq z9Qn$+$vuNPR*Ab#>zK=67wpUk*$wG4QZKuRy##$m$hJkFof;kEp*O{abdG#SfunE) zheYuilxJHT+pY>}tDxZ-(jC^F_K3eecBDm$i(IHqqm+xIcu)sc2KTyTr2M*+(wDk> zv8fa?l@a6;HMYW3L)@DO!6tO!8#O_U7Pq#_Hg#ZMQ(8Erf6mjd7+ix2RXa8q-@% zJwc#(#FSOOfQjOk6hwvb`UJHYmuv>fbx`BeR1XSwk&4xD0TrYQA%G~Q%S@v!>B@?2 zP}Ek+%uY?tq1{9=!syX-66p{i8J!X!HPuD1_!V!FN@Yu~a*Y5=B~|7A7X8#i*}SvS z0_pG|W!7QGnCN@~V&~5NSHx^8hfysz0Uj;vP39ftCg|Ny{lp99n9)((21PD9g+2Nx zqS3&V@Qo%RAH&H)xz_X~K`~S4(nxKTxwNA6C$S|2!RCCZ224Kvei8b#!ON|XZ`O45 z6b<}@gb}tZX>^i%2Ivi~N&UG2(P8NG-V|LqgD_F1PL3?H$(-Z3$1g<9MYSQ0+##vC z62|F9>zSSkg<&~FxKz?)4Q)`4Tr#Z*_a8{P-ypC5gcr3OO0kb3>!=~HG>09Y(P1x0 z;hqn2ixau^tGV?bmBe%R1Gm!{6Pl70O$jX5B0sC=H6?M)VfiCp+P=Dfh#-BAlO{I)%BpHV!r#GGk4DjCC!ky zm(?aVb*yaaSSvdKBT7rzeAAkxenFGi(z&vwGj8czZoW6N)^(A;JeIh8edY4?c-M90 zhW=QSOD%@+twYo zbkn*`2)0SyI{6^q9xHmU>IYT0L2Fj`1?&Nh->rYG`8&;XuxXDQ8{?YI5Vp^~oUqo% zt@X3pdCk^^TD<<{3lo!D^0xln(hv6YrrlpKtoHDFL3K}=Ke#cpU`#CD1*2hJ(8+VK!GlTsCr_t}EoD3U#9?^*V?J2sg>SL1Lh~M3jIM$c{*I zBPY6WQsX#xZjb{%#Bl#aUi9Qg=P%jW=tY;J0^YZXm!9+J4E{^vCDWKr8IG`_xEkYz zFG5>JCGO(9G8`1|TJGe?-Oc#VgctEb@3ZXZ8V#%YUq*(l`!!?vlreqEpkRHz_9Q=_Aa5Suqb-6iXB0O>U%Ec?Pa5Z-#5x85?iudLE%j9YPK0j44=aY*XmH z=uDXxb>=2Isq77`Mz!cazcqE37bTpCB*P*%G$o0HVH}kd?ot$soC0H+3&kT`I58nb zV`8M8PlPXs3WpO(MdZXuNen9?C7OuK9Vh*?#_*?34QWg)5sHL!d+9q8jhNu~u-uGL z2qYf_*^i=emT*3Z0!WtwC>nIG;3<6+MY3ZPaLNp#eeimO%hL1AE|A%1!Q!2Alf2oG zmETcJ3v2DHK>d9PNt8fkR~^^)IR>^8sGLT#J6lgsJS)+BnLr0&rnK=kH}Eb{#p_pm zM%TNFUgPb>qa@vgT2L9UB+&bY1nSe3Z`MuUbc18*?vcE9BI(4x+yOLdXnNR+$hEh zrpQznDOhLkhRbjeWtB2BY?L6O_lx@r|(=n?8>q7D4A*k$l%(L&Vl&a z-PyI=WUZ{H@>_HI{V!R$14-bQn`&umVZ~sjv3sF6&Leo@R}i`c8pDEMe#Bayjy!h- zIr%#9&3efyFu|(Ay2c&f@I^i(r*x_n}GeggL@8F0gA0Y)-#1p%LCzCO1LMJ=|n_q3q=d9)}yu&y1Et{PUfiY-` z&nfRK9zF78Y&YJ`bCdN^h_u6QLw>$>5IN9p+Mqp0`5zn7tjYL=c;ZsLUo*$Wiy}@j zJ}i!DR6sL_VzE5XRz*0a=F&IS@|76&wmHa$sDHi#Vi+9nzFm)=Nf{5jr@7R<#ZV@!H4MDG&SG;UN4CNYix+j%(=*UXXRge2o~n9HdLlhchs zp9qt4xRpGCfR`1=)Ay6hi^>h(+ih>PEex&KwW)P&KfRn2s%1N+F$7>Yh(ZFsZqI(W z>80|*fQpJO8bE{evlI{P2dHODMF1vr>w@DE>-uR-!15#}A=+=kBpPaFuxC=#n0O*0 z$^_e5x#<#FKz~SXh0DKn+js_k)49C;Mzh-3xxkRi*rK@7*tuS}N3GlQyUwgsuW$2$ zRPkgirdhyBqap?m#yB*UvOGh&Ud^ogmPQ@WEQ!(ckUeNN$UY zY1Gpxn&VP*H#mKdRvn5&q9oA>#e#YIR3*o~a6z2v=41s&NKQ@O*{lcHa; zX_PHR+g?spO}1wpxIGz%0~Z~Oijh=PGA<`2DS;I+Qk)CP;dA0dsGIxvf6)9U&76pf zr^Z0C_|&N}3^@#V2yTK-vqs~JI4)xF7)zEV%{rP$#KchC@6gP$6pMlbpHD>Nn79h| zke5v3B4{BhN5Q*8@i15=7KN9cguqJ(EFnn};}MOCM2WrxV!A^ZC$PT^gTKNGpaZ;{ z+%XrTKxmFd2-<}dxAJ?+}5vODv;S6@xdrdB+C zOUiQp&o8GveQ8&p%JyaKuB%U6u*PC4nhZp&D{p0Da zPpIzQOJ3F0sj{7fY;x7=p6^}kT&)jeDz?m+Gxbe6{qd#dbp4+7`ks~go;&sXXODq4 z8odicH=lp!`Ni|g&FSs?R~z?V!?&KC8OS&)uO6R0KEL}$HKRLG;4?Uf$jy<5GIpzA)wW)=gw0p;zYscdD zHCI4o10dWRlV6)uE4Hs&TW?!i!6ok5s#W(>OUlxiTC+cGJD@TL9)bs<4S|isOcG}v zI>4_;3n(CSTq%BMJ2Ey?|%o+xcbc4b8N&t{gzc+Q)PM-mNLoeB1| z6z>`QoLwM?`*JRaJ0zIDfU-A7IWCxj)>6I^;;n*ZvwYH!h-U=`su622pLrM7;BCB} zcXZH3h7~Bvk&Ye6Lc%e~HVHHovUR8fiq;b7q9^5#eBvs8GE6M1w;-e)r410)be85T z_U6!cqqYN(Vf!mn-wy=1!?~;>!i`RGpPN*0V>mIvMG|2-5P@VVM!+Kgmv~5uc1(pP zVjVVEuP?#H6AC8+X!LN(xv0#ILH){w0gV7?iptwvI$em9MMy75p_|QPI&1_eY#V%- zSWE}!qi~alW{BZ>2ET>CF=W!kiB~?)gL!(lIhTF`vg!a0((3@tq#TeV0qp1nH%avN zb9g80u>rF&I1gY!7L0P3%@r_PYcLY8$R>H#Q}C`#5Q1R&eaL){(hm?CrjF2m=>2`? zLhll@cyVd)!!67GAJi>7ZauT+?T0f=p6AjPZwsZ_$X5WszPKW5)*K(Wjc{d_LdrQ= zvrLG}xdZ?e8EWjs5RQg`I8~WPTVo335HSERBz3VwGLmWqqXKlsBLNwQ17OK~8X0NN zn*>(!O@yWP?5jFuUgix{J4(C**2_O{*FRmz9VDUiQ*%lJE+$IT z%#aa?7!$i=;=~2u_OluzL;j{&&WWK2kwyPrqFJ&#-~mES&ICRT7p;sqYW7Avy4J10?WZEE<(Z>FNHAW6PXy{e^2UfZf1)t1pDq(J^&gUK>@^ukl>P@o&Z9K=zzO;}eefJTeJJB}!4|c3^LxKH zv0m$6srBEfeQee?!(_bMZ`Qq2_oIdtN8=1(;_8Xn6Z2gQ!p-4#hJSc=Nmw4b)&2Xa zKfe5jm(??pD!&8^zd{`%>&sa-g4AI$svn^L2|FL5ADU2e#{&~`@A{NQWlq(#ca7b< zTyu|gsIEP0?4G5he%rOic75pkb=xo7ZVBqpmsh(5_4Jvw?lbqTjKgvtF=h*X8nhIK zU%nB&>L4?wgUoRL!RE<-p-AS@-dwdLApva~nkk;!I}~R7#h6hX`#k|Qrr~G z7psML3|fKdx&)u$9@i=9AW2_K+At{_^ad-D5usx;e2y~(NmgCb z3Pn0lemxsX1v)PUunM2hNZzbB-ppHqEyk$O3;EDo4FAJEgCa;OpOVH~3-&(?)zw+3 zpZzQZ5jzx7dL0b)Ny^&Z4)sF#f@YJI1QtgWoD@l*f#R4vt}lsA#mJR9RDeMv>TwaM zP5{GjGR{d;5MJX?em*{S+w_v*`O`jUhWjxuk?*q8>&yD^Ujl z9+HXakyA%LjhP5REfs^wgYc981ea-a&snqX^xby)e&$|v?t{3-*wz{M3gezHTim5G z?p0=Q#$LH@Z&KGrBj_STb{mX$^=RMY`mXip#etLA} z^r#v>mp&a`E03;9^175-ky2~yOLturckK>6>S^rA2$6hb#P7f)%;B$+i(XTX!JXzL zPz~xIjY7pal{KAiM7_uxBE&>#WF1l@f(I~QOyxbKFQ3&d+dBb&3&g|&7y~JZkMc8c zxo@H<>M!WTN67UNvVVkZe?z`cEIX*DKN+Izl;x9#a>{wX88MET)T+soVXSWqyf!d9 Q^iAOoQw>^R>hvxC5BH5j#sB~S diff --git a/scripts/contracts/__pycache__/inventory.cpython-314.pyc b/scripts/contracts/__pycache__/inventory.cpython-314.pyc deleted file mode 100644 index d6ed2a49888974783ce886a844c466d6687dc3c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2736 zcmai0O-vlg6|Vl78D?Pkx3R%24bBD{0~33#D9bvKu?SvR8z#+oZ0v#Ap6P;V)6?Cq zt^qSUagcH$H+)*hisa293P~B)gE^D5Cz0KMwN|}D3?`EICc^#m*myVv?1{- zDXHtddhdHxuimSA)n^)`5d`i1FaCJzCqab%%m;pxYRt1cz-%B5QM8COVM=&Yq+$=} z#VLtO3t}%?kk1QMbfVrjKcwetY@*>%=5veUP^Zr$L_-Uq*91-KK$bx3H1RZ2<r_9`W?j~V7`P2f!RDvOx-d|IyD_z z@p5@w(7h3u5-Y&N+QzVHE3+=PFCvfq2yz9jqZxf$m^2Yu0~tA;eu2Lx4jqYl!q3}bSRreE zetcS)YYZV*PRWVD5onF|bLIv#(G8~IY*bsK&gTwkFS>@0ASx&7P|LaVHJ_T%s)^QMHSz=U3-TNAt?^}?CVk^NLI%uv3Tg5cK?}Hzsc@p@OOQc-ziWVY zo7`r%CDnMu*VdP9dXW~=>QYU<&qGHbZqpIl_9KcK3%21@!+FRLZ*FT+NOV;BqrDbM zHfic8WP=yD4+AQsJ%RRk@S^ zxBbLF+X^~dx7)*Ty>ZMVxtQ*nC3$d!cqKUVHwTt6ExV2fT@gN==JI6&TbN=sJ&^VW z`vz_<_AQ)I`vyh^$JMl&UI3w6%qj!pqdMH`oT82|*~&nA?TiYV^nj1^1_3cV2C|Ao z9p~D2Sis@8m!Xo(9bv+FsrACZ&GZ^90r>PtdPE%`E6MMq&wjV`^K=$uao4;GwF0!>Vl6ij>8w${rD;@rYHdNV$JV}W=2aA{%CmwFo3 zjq)5dOy^3@jq`#S2lvYVJxCDK&Y7-mRkTw!#RL}gQEEENWC&fJ1q&ETlfgoH-8L*t zO5*4!3wo7$%R;Lr+Vo) z?;&>Igs%@mwKxW$8tYRfRU|STIG8>UK`~UZ)b6L+a*BqZYZl1yA*hel)DW{(OkU`N z)v?;d9VJlwnY?T{Sr#S`n+%2O&P0M)-I8fTcranY9JmN?feS8r6cn(WD!shGYgLscx3AZOn5OQywWi_tHb_yk!3=LG;w(| zbY2^DUsYae>_RN>6mV=6+{a#Q%^@qEtLJd68oQX61GlLc^WPgUcK95+>nvJLb*8!< zauych`|!Sid%A)4qfHx=&qdTTE$-HJZiYW^@4P?p_|(vou5-Ix=N^51uWR&4`x~1R z`)wWXj@=vEYdgI;aUi3P%i`vx&rfze?0V4k-nCy|{ME$!6Tdt8`c`0nV(R0|A71`w zX16)M6}*3AdwsvV_u>2x=ePSm>ppvrJX+n7_j|wc)9xR4Z+m;auib0hlDG8x$M!qA zew2N<^k8Y{)zJ?w{W`h#%Jk!o=`ESB(fOeB0HIek3Hqe8AU#8?x_{3mbiT$|syvA8pz*_nQOCwuEN%h_@1KM=Yt#Koso0#jWS zCqL;fgLAcHzj2`#E@1vDM*A9DwR38D^18iCgscWbA)E(gbe00@^$&I zRRX$snQ#b|@5(UQjQl-5>iJdxfGzRB^C;>ZeI7=~EvJO>`%baL9&P6nAh#1(tF=7NZ9ycB1BYe5`%p5VZXO@VCVQa(+Y&tF+vqfyj z>=FAhN5pZ=8F3zSMO??0MV6IwBEOQ0bDXb`<4QTCu1TfRuU_El6PI|SVLkh=a{Pkg+N%sI-{Fn)S6Nh- zI~0t_-rmR&Sq{l6FP=KpsaktO17WW$sg`gk;O`w)Ey`ezB1P1^>yqs68}BLI0N-RPCYH^ELgIG$% zuji_^s(fTPjIIgwye#!bRC`zsg(W%SmlW0L_o-HYFd|)%WYw%hyb+{RBCQwg%{yzi*KSjJ zf3Pp4nj`*5KvJ!J{-MYq2Fi+oQIPkk8EKHp9LDe}PxI@Lvo6w)3Mh47_4{tBd^n`w z{fg8(DElMBs=Y_TK+8>ok$yDAD|-i|2#P{ef=G)(R6d9}G^4j)8t|$PsxL;qIpiCr zd4k3UBTw_u9!wN3Rln|KB@}E?ZL*|Qxes9s#P`BZ`-aq z&j!7Cbx8I{(FJyzccCCkRMb4Zif8D>D24BjM8b)Bpgvl??{JvQCJcpQz|5;qkJR;t z2Bf;{-atT7>TZPOt4i40E7c8nWmLRQtASES^`=R$)J4!151Z~0e~&+aE~yO!?uX8zksD){@s$%*lNUa; z#wt5v;)PguFm~+*l6=*0gi;5ac)!1YL za+{74I&L~c=&b1ip_fd(g!)VYLP66FLXv5SP{24$=&CVF=oQm(9;l7)B6ObbCUlv< zLg;1w6+*A^N6kPd%pHV8^F>14<~~AxbC^)S`5Ga`{2HNV%W(@(n?)qlY3U}^YPm$H z$#R*{S1i{F4OwmyI&OZI(5w7wg#7#=D^M%nM96JzCv@7{Md*UHk5IogKqzRvK`3f% z6M#+$=LlU8x(T%lmk148FB3X%^$_w3eT4kNbwWcziw&q>I7+C)dW=xiaGcOJ!wEv& zhE_r+Z5P;`ZGceN_6nibY)9-sN9`vGowA=JbiwW=^s23gP{`Ix=p~zvkW(UPmh4Y3 z1-V|D-+I7w)Hv-nT)fV8aUxf*Ux|0p*;_4IEORv6YN^gwFr{AGzX1N+Kks;hUxspH?0g|P646Oe0K3u1_5ByhS zQ}Q7^DWa#8@pbNzqj2Q>v}5h)SL2Q?<459-9TN?4$AQ?vj=1A=OuQI(ycFYKTA;Zd zte7vexo99SZ$`0m+9tn9adTT{O>k!_G&?(vhU7`bUndk(}6J3D$;xfPjF%NLLm=Cx@EC4JNmjf1wD*%hdLco;} z;zcXLb0Z~UG45B1D*;!FC4g(hRe+`9YQQpa4d7a_6tG+@1FR6&0=^)Y1FjP*0N0Bz z0B#W10d5r6M>chFkT|RU|Vkp^WxbP&1{&^i-fI5qUu7P!N!DL)F zuRQ|`vFKMR`~rx%L+|fYbdpj~QZm2Dt;e)($D}7g1t}QtseEq;;!E&JQkY$tg3=9; zHm^v5zVvBthBkLLtd`7gSuJ})%t0aMfcdgw<+pd=;@=Q(@z#NF9T+S8pyd6M=@r$t z_}>%e&wo;+LGHqn!z_2IGqN`fvB?G@Ln4TruIyp}3=sN~3Ad=wP(QyxR6W+97M2ecPL3ra5&^Nq z zvW+NC=hT98Rw$VgN=6IcTm7BY-z^=JKNhOzw=_9)GV;F8)zMtU)!ZO*n-G<~#i9Z6 z`dTV2quvvjGXzyyuNX1VnqZ3NOF>^a1U0&1z^^D!UEA@5h8t=#>#yU%g*r=?21v8^ z$e}@~-#I%gBN?%Lg=7W9TIt$b{5OT$!pO=gVbw#y`AyetSFE^le8Uf_?^OTunu%-Q zulrbNoZoxNdGd8u4wepBGAuUzlM*mPNQE^HpRP6^wn^Yd=!jq>l=zhnQd^J8J-Z&a_7uB7$) zGF(xwAEFq>xVq^oE5(;*>uya&(fWJG68k$X&o;bG@9-UaCo5yy-ERmZg>RL-Su!oG z9^D@oD#r__gxY87@V8NDy0F8bS6RR;Za0 zYR1pc2)mzYm~VYa!wlkiW@(b4x1`CFwA3#~{MTD?M$EHoJC@FgigJc?qf-k-5D|31-H#!#lHOSSfdLIKGeWP(aHOGB`W zB?;^hxn=r4e9`cw~yn z%DAw7VpCjbm=z9935V{ko)OxfiG%)(6->ljYh3bJ&(X)z?Mxz1+PTQpW@`7uJmv&l z9EyvU_M8e2BWTs&RSi8UJ)g~sq?}AGnsd*Fvczm)7Jd&KKM*SRFM65Pep~J#&z^&3 zph8>&M7{sTyDKIe=j-`#0k*>_VJ{L)?pomSv#bTKvsca=p@q~_!J&D4bnyioV*CWB{IL39$* zHZiM=as^)9F5d*4XDlgY)0g@RM(o6HRI0z~a?Rcx8P4Dp%4Ps`S#?cXe~q4)5;i|m zcg?U;mdtxHH=gl=b^28q&rzR$50kqq*k0xfaNT&-99Dkc*gM}8xbQ{lC88mn$2L}oix{Lt2~0_tpfiLK1&~`R!lN2epM^>gWx9- zj2gXLm>r`1j!|QE0v^ws=9-(qAav(&I3!14d>};{oeL|sr?=l5ydo*Ampvdd1)dPi zx%+jgWk{%qF26M8=^YE)3WQks_KB9sV|V?r(`RElyJF|P(a94i96O9lf0wq9wM-^|u^+N`8h-X3OpPS`aQYm;Con0%%$PIAx6 zWYXmNbAe)L>6E1dOD1;5DjQ?v`zHJD_QwvKik*IGx}^JK;nMsLw4($z6Kk+#L>nsF zEJN3zb3|S=W44iJ6#Sy8fn)Y9Z}1z)+`ndc3_?Vcmmz5R96(*A6{3?_f?&SyFh11FQ#K%f)_L0$tZRnqJJi*GSj*jgICOj$Z0ZJ&;bq zCjT+?_&3}>XFRXL3Z{zL9d72TXp=o zBhOh}6_mxdX3*nN#B-j5{#9Gap3_D&&AbrhHI&z`ga+ka z$)ogpRY6avI>3!Q39n1sc!%^GnKs72Oa2MvE$)+ICZQh8PZvu|)9tf&?VjLs*i4rbKw^bCU|s@8t5(jV~m$UmTbwti{I z=Z78xmsl-T1ct;a2ji)#%?D228wtt7sxy6GSk2R!yWW*NHB$b7iZmfkzQZcjGZ^%t zCwOAO@&-gHH2pUrRA@ot)b`Y5P|LC~L;$=ZTQe1yU-oG{BkT1`4kLJa}0GGTZvB>w;6A>Jj^Q@JwB7S z@nL@PTdp@ww{>pYOFdiVi|dDDt|LM z;xvA%akOuG#m3ncHB&2UrdQP7I{eU8Ji2d8imj@NyJ}{g4O7mB$qh5k7ax{y9IqN* zGjSxgxpDH?A7A;?;Qe6i?1kx;i?c1hsTSXKOJA)2T1>f)iVbV-rAIZF)$$JGt;4^2 z>X$3I@;<|-C0zch(S7l}P3WV9LW!OEx9wx~(8OCa*WposghT zMV>%^$^VqVzXHIjUlk(KK$!8V<(;?+VgG3OuuknVjfi}o0CWM4Es@k^n*P-u(mbE; zm^2>2Q$e)Js|*Gr3MB_|YjnLYu{70|N*Urz?R{s zn54wL&{W{MfwWrqyP-tV0CgQnr>+I4F=Nj}PhZY;W~G>J^7CL}pW!rrg7d8>tqqzF zyiq|f8T6B%Tr{CtMCw!@YjJ}rt1R!IA@rWQZ&XZ< zfR$${_94LU(UVl&PfZEi3|B6)YRf7DdlNT>3-bm}h$9ndzcV#;IqW zrBlw*(XO$!c-i(D=Z*yPe$V|~_f&c3-Q6Ene(XLoUEcYg`#tyDnNicFoiom}85vH& zN_76?gYDDhr}PYIQPZWTW}F>!nK(ILzuQ!ESTNsr?E-vYDj~SG0`QLo$Kh2u*!pLz zxxblg9ZPBa(pVBU9TrhmkRrj7cx}yPkpDe;fU%`^c#q|pzVahVHGi^hWlDjr>1gIy ze-KLs*q*Su3=DfheMy_Rq8`e{V3u0K2&$2Ad09-6h@C!9bM(S=(Z#HD^mB8j8bJE( zy9b*#neT0KH0{YTU51`pABXmu)X4G1M95}L)ERx)nC&Sv?=+2kAtz6!ji;qezLOLm z7T`uotPf%S3Zk6rPFhO3jC!3Q4~_Ion|$-`7n_W2tF@`P$drYLKw`?8q5?QJTg*q- zB>dbf`uz%)$k@3KR6x%8`|yQgCLg|!W-&z?%k*HtAH1p-q}wt@e0Vyf88nGw!)=}! zH2)Lj+5nKroGaLTPrTccdbdY^x4JXe6kj0I@6&st09hFCLo8fheDlTc)sOT4xRI?{ zAGr!fHvI0z(dxKs!>qGr%2_kxtcB%rwqWyA!RCMAitXu~EjSx5IQuBSe2kyT-w12v z@-D;YR?PP)SM}!>x~C>$^>>nk&us_Bfl1Z1+t9uJ0E)nm4-cSFr0! zM{`3Cy7-IVAi797P0ye0VN;hW+e#NRJ)w4yew@j6v#&=}+BG)E7NRcN8)G4Zy5_uN z?mNXrS$$jEQmCtK>rztD+Y&Trj=FU9k`bajgu1JSiuP#!nPW{`VREA{P?)z(l`T!2 zq^OXmZ*o*Kvvt58kp_^O8j|214?=N^MVsd^;Fkr7&Y!k{oZZj-D}PF)-7+| zYr9v!3D@^mJKYZ3{Y?U0?{&BZ*8>w>KX5qQYg`YC+4Wk7yDkTn{_NL7rHfhdjoIuu zy475#yD$z#=rYZUZ00-((=DUwES-fWRfg=@-pS6{2!2L4!t%qsi_}k5R!GXzo*w542B{e zIw=E(GvM1Z7^gyTMUzuBeKMCk{X)fkk19u-l}nSf38S&^z2J9((`##|SJc5|T)q9) z;gQvGtamO6)gEVe^ z%3>1I_z9E7&CA?n=KD2Hx7~JsuYl_Zc87bl>p>B_E_1kR|396l?~47uN9T^(+B#h& zX2xatUt(@YSNY+K?vpA&Nfop;1gcN$B(=+*B7uzk?zuFot>m;lBM%*7q7x^MAn}GJ z50&FdmXmuWx6~58PgVFw0M9{*>p+epMc-&0wZJUAI3bF&1!v*~XVM9=GlLL2L5OEE z2vH{Y#SAl4(jYwZ_duBb9g9!n7Z9J%uLv(~nVC{?dFBDg_M)Tti5>BzQk$EAs+o{W zZp~^t={gbhU@JT+q^ndR#}TewD&#Bi}&=)j1*A0%!Ur zZqDNg$YaltW+1{2#UiF2q)4c+%Vbit+n=tRfBIB!x>E=ao_cEEiqA!~v(=(STem`| z^!dph1FkK)PYP|mMpvXiOX$ON_7_h7iWa9o(|lL9!h@s}TPDjO{^-0g56(si4@Tf7 z2YonNEDgcZR1xuBxgz;0lJAsNyh*L>Sz@Tl&!&T`CS_Q`#?4?44m|6X6r2Vo4+d4c zG=vk#BIMGE6cJe_J0knQP_@evs>u9J zhxvqPtLtryktLg?N&W$R%!bcT`>@~+!mmebw@2$K)t!r-JwqWsp_=>@U`hJGC(hzW zMdfc@{?_HOGap=j|MCyd{J1c7$QN($P4ALoSCpB-q1f=v>A_c_?lc+ClmEy?V=p1e z7$x+I(M^YQ9Wh-ZCOus>dZE_VJxy#C^h^Cg`#X6!e+P3|q`2U{HO{h3UV zJTw>+jIpi&;RDYN+0IQ#Z45H9r!k!qx-EKyPRR4^Uo0=FICDaM;vVbQ=OJsOT!Mn= ziv*ptU)f^df}?35WD~d?9$c(gHFJi*C}5ft^T~drm$> zA+#H!>;r(q6TEkDz{xkAx1A$hGeYS@SHY}n?UZZnJHxZCs<^9)1nG&}C*Ej-5oj5F z3##z8|TZ3~b6b9u)^`3+zo z(F!_~P-(2I0|zBP;96ogIAxHIO4Lk>LPt@;de|euIg$(y!ye|N?8SBh++reh5TeK1 z%lL0sE(Z5}%Vt0w)4cSt2ORw&6sd7 zA-G!c_mQhrb7;B86*R3e-&^Bos>z&kq}?zE^XoAN{(7~nT4sC`&0kQnN;{GYq>@Fm zdQQD6!wFkw($C3qt(qn!|1A;6{{iTf4J7&h1ee*>51DBO7D{9#AP}$-Afck2Xke$? zZn{ls8nT1poB(qb3e6KumT+c*B6+SrHScD{l&Q zR{j~lm#b#PHqc3?r?nGCyKzn_X%1d;SS7DD%{gL@<^bomgwgQ&2RGlpIbFJIA`mZi z&o~cfRT!FgkpXbN(Jx8Qe}$fZJ_j|-X&T`bq6ClDZT zjldv*D1lcA{1$<)6Zi)NzCqwy1ZZPaexCr_w*4=3MH;I7-wFH`f&WPWOpcTPn!x`i z;6h{M0s@5uRuWhZpcZ*N^bxwh*Ml!WWq%JgdK9#;z=KaWp}G5TRz|NB2zWgB^vsnO ziEPNu9b)TJ`7phMKpFm(9|QdL?_f$iHX6-a9t#HZuE!Pwt}I6LzQ=;myeS=+aBFAj zOh|$&JWa%9q~py8A3KczD9>l00xtua%xgch=bH;3Z!nuL8Xhk*n@<&D^AW@6`4)4F;Zu$vbNXk^q0t|}SFLFNO8FQya*_ZE zR`$&-`(A`hK{Aucv?x)9!Jr=>)4~eyeR{wXVA7e^caE?#7Wq|*UEBS;g z_=L;D-_N;ov)nlhgF&)Cwi^r$=u4dXLhzj7GojgJXfceI%&w}QT2=iS$8JA8Vc~e^ bt>}zt)kEI-J14((^6RI5Y2x_RtRDXtDmL6U diff --git a/scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc b/scripts/contracts/__pycache__/test_contract_tooling.cpython-314.pyc deleted file mode 100644 index a19fddea072ca4b8bad322d2f694579575bfd399..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7690 zcmb_hTW}NC89uAsl~%T7TfSfeHWJ|yWWc82m^%amu1$(@7GcuFuvtrMydX=;*%ii~ zbjswRF>PnUluqjgrliwO2ReP=r87*QfG09}v5_xy1Jh0~)5#l!%+T=E|3AAcSw?|` zva+$|NpM72$m7Je*dq(ovaHG@(;|om*^xOJ%Gena-7IqH!Cs~KI@5cme8x3LvR-}su>@O@>XzfI%QHd zZtkZvO)Wt_bSP#C$+VilTJa$*ehL!)6Ix26s;MQS9!oGXTHKPTW@OSSLxW=PX^rZG zqj7f-c;}qVqh3g;u;#~z$;rg@9IrC@MI;R)t0$%)dq9dZ57H|OAbqk3Qj)zO{jv{a znJj@Um;Gj-kC;KZYz2vyTfttMPGsX6jcJBy#NF57TLt)sMUx&SAaR!T=gZus8SxN8 ztNTc=v$WXAz52Ko(gx*tg$dMgp<_Q{vd`1z^a+bhU^rY)l&2DuS>>u>Xw=;QURF(7 zWp*Y;v)WZ2)=f(eT3!to+OUKZYQ`{d0IMSrtE+V=J*>5!R+CB1XdOw@Q$|LOYpug7 zrP_O~Mx5#y(`b!5n`N|`Fl)ulDrP#J)Ke!~GNaZuR+7$WDK(=jL#m-bpNf&r(zvG3 zbQX#gHI-2EJF2ME)CU0phSgQHrY$etQwXMIyb9th`DcCOg^_cCaqnFzFef!kNevT= zKB=CTRzm)x%5#+y!mQNv2&$5zJ;cRid$<8s+Ev&tF2li|a0QTOJpK9okv)ALnP0aA z`?4=t*zD}&c2424o-D7-Q;&Y1TdxQgZ86g8cS{QQenu{s-_E@$93Y8CR6^K)fNLLL zmG8F`s0U|`*9n+GBYC5okYz;n9t`>M?mHL+gET&xa|wXa!(J`4p7a^(v$))%xXp3 zfjHd+|Bu#zcm-Z|mc&RWEJ+4*x{uomPtTCEVwk)E(`VBJ?s<)ep?JntB+~Khu$BV$ zh%f*~hIPXLxJL9;WI&Cd(o%_@tK_Q3^0aKQL|Z>_{Hr5$5!@{Q4g~9&4BcfND}m5L z_cp#2r+a0PriT>(HmIVj0J@=2?W7imv9#<+rsHbT*wvEn9=4Pii$Qz=^P3FEr}_sb z55F}T*?+rkHqw*pANcB#0Hq&P*K)DWo~YOI>#5T)u{0f}*p^}`;{l2qVDV~d^r}c} z@F|vCURwbv5IC_o#X#(W`bV6J?qC}Cg7dKsyu7ukhjAJ2f;da=OKblUT6Q5k?!6bR zo~&7O?ez75KaI@=J7$9&_Zpfm`p$Kacis(DeRSyDq04(O?ww-GmJ7!(@4dYD<3cvq z7@rO#9*d-6F9)sR?@qAeiGl0%hqewuyt#Za$Xh~rN1eY2dU+-=pJIZ3TyJ*udfimv zzPyMjT+PaOWKVxyzwzMd^Axn#ttAVqp`(7#T>Fwm;1=&Q*(B)JeTq#g-IBt+zsgN7 zU=uj-z7d;94x9KHn=EMM&ceL|H=FB0lEOd~sN@n!x@zq|xAzhQ}cd-twJRsjj9>utAdPY@&z00)0n4 zbR`Z#wAE-HM^M}@tG|Q>u;vaisPVX-0DXlMaa4f=av-TClypjg6B{fT<=N!09Nu7{ z7Bg}PbpFPBK6}kS)4c8aiK*uOA07Gek>7g1G7zmFR7SX8ltnm-j7ghNgh7;Evlk%L zr3o#QOphX^Mx13rN*zX%&R!_G0^VmVTp!vFPjLYaSZ{~p-kFNMefzb^$j%!;ahA&Ar%_1R#3jVjU|UEDP0O&90DNpNpgW4jD_sj>A+K#`y!K){VRgT>O-Ow2R?gV!|F#z=BW}vNOwRnqPjB<5($L6o$y8z?6g7qqc z$t?R?Iob0}9ztzoR}l|YgH@Bal-wG?aCzQbz(dH3T!9zA)~XR49ugT3EokNPk9!9m z^5SnE4|$)$aA;{712Oj$Tm#mPup*-nvAqv7iS|oKWopcto8cf z|9M4)Qc4ZZ4YOg;Y@zFMEC@0%Tx|QSovHV?o~O0%Wvmtwzi|Y_S;CaH`TDxs(&W}R z=YsNVP%f>puztC)>?*9C%Qec=fy1b;hrvFP;qOjRw(XONwmlogo0W@EZY=NMi}e*X z2$n;7^mjIhVtw5VI*ivdJRg>%j=ja}w1@w3vAk5NqIpXM53kf-Q471=m>&G<8WPeC)> zlj-RvbUC_{GKX5-pVx#!yhZlRw)_fz!g+*7^elaBMSR5qaRSl4kkr&)Xus5Uv3>mT-F4p?=O+VerljaSsp@>qPlD%9&PdDWBzUssO6|1N;vnZz z#V1CtVZ*hCnTBn%()MQnWZ42!ryosMc7X$tcfv@X#xh(X?9<1&+Xb#27)Pxf0BAuN zX#VEPVo}apl=C6*zEyi^e*02zlYQ>^SU557dO8!t_st7p6=vf?6KK~DAS@eO3V{Km zHKCo>k`Ugbtxk-uWmFwbT7o&6(JY@1QI-=LwaO5k=@fVfhUGH=bHl1(`8%?DGSQtH zOj~6cihY>CcUAykmxmHP00Tn&qT*20V2UHQylMtQiV3U24r%tJ?GUGnU&#$xAli*= z(k#aDT8867$Pot%Tnr&kJ4j;=Ax+zXXlrW;?0&PR=-(J4AQ)aR36tR+GYva(H9N

3rjiv}|H@Mv7ipJ0rDUdw)jScjNe!6uS$!UQjkR zBX!J4-BVKc?Urfj`z)HdbV^z}(fPaXD?_=Jn`fmhPa_=Hju{6BY=lGBzIyWrcb^dW z4&V~w)8Wd}h%eTT3Bi){&`0J z_m1rMZvErPD!Pr4C>S{@$^ik6@_A~lA?u1x1-pD;=3sD!xKG@VhDs)rznOA+Ynb& z6AZlp_9aBILW&Yk!iFdGxEa^rQ;7oRZE)U0Xog1o>&(}o71$pN4B#0^IRE_+mi3`W z5SKj+h@!?ltniA*xQA7|7~>w6@nRoHLEM3bTOU`5V$%Zxf>8zQ2xmR4>M3~fV!8!7 zv;+jSiyIO8C1^!v7|lrpTuIr2anHc%Ll$D_MV^to+iuk!w2N qHCMZ8s&>_v1Tqg!azqG>k4^K7?+Sqr4}Ng)M~D8y6JZH^@&5o2@e>#T diff --git a/scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc b/scripts/contracts/__pycache__/verify_transcript.cpython-314.pyc deleted file mode 100644 index 1d06712bbe1eb7a3a07cf21c3206980cd04ab2de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8050 zcmb_heQX;?cAw?)OZ=w3zi3gGY)jS$C0n*^*|A|opRq+VYnf7PO7x0cN$XJLdUq*V z^cfNImy%v;rI%imONuCH(b~OB<3s+aTcD`ZT&_B_#Vt)KR5m&!=m*-Oe>AaET%G>Y zH%l%_>4c^zI)Y|qXWq=bdGF24?>)BItR@60{x4r%|JaGpKj05F8M2ipuM!9?Aqt6T z5>do3AreZd5j9FliX@bpVeODk)YWBZ^rBwrGl&MIG>S$j$>E|QlV}<;i)MvRJ8T)U ziq;{UXdAMN_92Jp7;=ivIz;IzkXTIVAupLIr3@#=5ehFMREuZNtu#US4y(-O%X&0{ zC}TJ9jFVVCQSS3(>V-K~G@+wR-I`2W9RjM(!v0LXE88SiO%%htRX(+$fV4|psb-?& zyJ#kADDy!WJ5dXidS6e$G}$V&*Gy_Ci^`cgJII7+Q2v{(iTC_;gbmXojX&z%|A?jDZLLr08XY>P!Bi|!c5WCmyGLa{i^7j_E_M?*UkS!_3X zn+aL#qs7@~ja=Ht)6?RSjw2nNN3x8^c-fw<0uTK>ALE;KvX;7daa6WG8;goO9TJsh zSsRJbVcC>lQ#QaxW083#loh-pIS1q<{6F~~nC%h@gaS5|%7^#4E7c=EsqV(BIe!G9BD4>2S`LooBnp(MEy6PkaM`6!i>yG`` zchgW#l{Z!2oWt*!hR3x~LVg=aq}Xo<8RX6`$iPUI6!O41{f@o(ph`VRoqf;#vhV0G zS9#=${l!d;!dve7^B1eVzN2}Wt2KXae|2yg_FTO}9nXAU(Td%7OdXvssY;b9r$f*b z=x}e1SAEBh{{JLW;xC!D@1d>gBN~-dvZc3CBW$C(O(<5&Ud> zAdAu*+rCK8MP$9e@be5W>*pA8HWp441#VrPygktpxSh~lo%Bq!Bs76rGI0xv+cI%m z)^dO%Wa8|O*Io-;W7~W(1lfL62QU688i3s?w??s_@MX`nx;tD_xi|kNS^}VoUsn85 zVqkTQWQk@CkM9l)U33bjX(nY&dxuPLG7%K;18~somis)ju{p*wPe&q*;8}?AFA5wT zVmxy+50J|vgm{({1y3joVgwItmYrS<0;Gv%I@`HLx#;u^6J>64{293_L(qOY5(@$J zI@6x(!tbJhAHRQv{YkE~KV7&d-o3F(zdQ5R%v$K($ZF)ng{K0R#mnyfM52M7n__3; zvAE!l@$NZR5ZLHU9;~^ip;ac_iISUb8D`t!fa`!cmI;GeL|Tjsa#0RN%7!pAO~)f* z!qwxBM?t6YIY5B`^Z=X10d{6axMia{L^j}E15Ls~b6kWCDG1YZU5G_t{?IHlM`Q4& z;8K}5BAc`I-dK1sQL}52=Pq6JsvtJewga)|Bz57(Mdqd`n*`92 z7-6Fz9GxJ7-O5@;S2_&21(_5WQP!~{GY9=~4ow;28=I5$Cu)% zh&fpk<79)f1=zY7PYs74#EOfu72h@>o;lcx$nf|C@&Jc`fhjOjz7AJyS+fmhO&aX~ zMx4c58M22?CbAQ#Q}z=}LtCvKA9(M1Q>~}dt%J$d!Hw48l!4$4P_kXwYCN!Z<0lP2KCn8vG`L+{wXEN2JNCiky~&NXXZ{1Bk}#QU zd*-dgTk~tg8voZ7A60x@wLUFfp4jwGCeKa2(Y+C3(xKR+P;7(a)7<AR2a~l^gX?jUzmS=HsgXklnae%ylixKh?2|E}dM@d+HfV@J&3b=s2tK=jeFhB(Pz!OZNuf zJGRz&|Hhv#{B+@g@Zt9!-b!{;Z!D}5KUlaoxICCFzqD<(Eie4m>DA5+bM1C)eUE3D-T6xOS zl(uvxEuHrpH!R)Iai`~%o)tD_c0V$=-Y-vg_9i=fA0AD24nFD}+;Y{u>wn9?Hgf++ z%5^;L>PfnK9-MqQm2wTIT_Z`?h~&K{O@&h~ChcOAE>^nEr(D9acgya2WIwQ7-mrFi zEhZiFNsZ5^$|=b~VHWnKEFEdf$)x4vgM$yNHha!*ST1ZC?JJ$HS(Xi3M#~-JE5?g%JZr226eat!L=5ZTICOJ9pE4Sr#;ZQE+3edqsnK)Q70Z!Svb z15)io$~Gw(C$}v1Yu!@oz`9K`k4f6Gr;pDO$bOZ0`i&l&%F`!$R53~j`1bzDQsHgY ze$-SA`Cql{_jZ~-F773u_@vSR`Oh`g-V?^3*Bc<;0b=ld498yk#H}2D2rldaT2vR$ zhcC=sN$mrdk0=N(IDPoe-<4Ea6$%yv7x2xjBp-ezb|rA42^AO?1Q#?M(T+HhvN_NI z08maF$U#n@W!Dt&3A5xNB@RAN8YQCVR^TYU;FUy?;MVH)aBKCGxBPlaTL`2n9e#{Q zIs*C85G~w$G^|G`>j7ujNu&%rW-=Plh(%dpS36!I;K=ZXkPlzfyHb751wTauK?iX% zm}}t-fijicZ%}0j;RCbUssvw*dxcOaldmdIrPdU;e-HUkrdE{%N_C|=RD%NG+GUd8 zm~Ri@7fngvsB3q^eGG{GWDe>REOMhjF1VscVc1hG0Paz8uaL=T&Y`bEE;OQL(J9h} zSfU2pfBdJ0QNVE_&cm#3)Piyr2_dCg#Da_Ag8A_u&~%{e49)h$U>`FF@l62IO5hVC zEF8d$D=-&u03YWtIt|4*7Gj{WX%(kV0Z3#ZA_@bi;hAs$wvmU4=7Dr34gpI*tWZ@k ztQ!y;ARTe>TfmE<*c{*ib}GWSN1pYmXmrOR#_0Pf{6F~v$a0>@0K8L9h&N*VkWCpA z%pN;1B|otf(Q)6U7(*V9KYA)K;^`p6Czv_5Hsw>FQ-IHhZ}G&Q?< zjZI(Ulh=4jcnO|snmDI<*ZCvo?+-o|@S%Fye$GMs&T+1!c@5} z!s&qEfx^R4s6aI(o4g-~djb8Cs4?p+O zp$E_Zr0av8Kkm6tfAGxRXFfiZ^;a7X5Q!#rOK@Z%;vb8+7ihsPu;49-@GAoy3NiRV z_qe^yB#(EIsNQiTb4ONOgcCaf1kFw!>zT)o9e)gGm~FlrGFc1v!Xow{7*45a!TlCx zBZm6Zu%ui(3bhzd^NW3e^Yu{w7eJfPaAo$gqUI{6D${7TZ$)=T2c+w_RNRX4~(ytr=3qO@N`y4ym+#!`!}I zS_yDkIk8Wa67sTM&2s-%Q_FJyO7o_rc6IEL<-k9dRwl7+rz*XvQZIDdOQp&)8}>6> zrB$nkH%kvl2fLG{C%)|NSm+t#wQwds+yDQ!KxVLg18-L9-%ec^4p zbhu};vS)dC%TkuMxRU_+O8=+$s&snMS*ACnwoUmrrnBJoudvN)G6Jbm1TLEL|S(}+qNzCnbwam!Km zrsZ|Z>g7)z4PTMa@a>kP;n!AVYXPP*e0?jN0#UzL(QnlLMKxp}H=iT=PZ@sJV(z!< ze|Fpk^-rv|{jEixG;46Zg~0Vz64y_e2drk<7!1zQY%~~zo7Ef!e;LP!;8e?|d{hYk zV1^A@uI3s1v@zk}Z|EIp^lTLWNY}hiHUxtZlL-c8BhAmiFK-Z6yAFKvbCAgf?8ipf zDaC{{rvPsPeD6g;HsLEITAb>_r92k8(?Ud2_xhp2pQ|g28g#6rRNx6 z!FGtmAkH^YAQQlYtD3 z4Qh(NY&<;|3&$hO8UDAS4Z~yseuzS#FoO76OA^{AMnpJ1N9NCw{a2_`sT6&VO#h5d ze}RTJ(a>Xk12IlKzCctG&aaLmtz|i}L00`rTf2pAudGYm&DQn}>z-KL|AB_HG@Xvy&mXW0a n$$TiOZTd`GwA^^-@T-Sc{A=enOb0*J9{SpjwAK7;!18|s7eV`8 diff --git a/scripts/generate-api-types.mjs b/scripts/generate-api-types.mjs index 2f3144a1..e357f028 100644 --- a/scripts/generate-api-types.mjs +++ b/scripts/generate-api-types.mjs @@ -1,22 +1,88 @@ // Standard OpenAPI -> TypeScript generation; only component-reachable schemas. // Committed declarations keep Rust/global OpenAPI out of consumer Docker builds. import { execFileSync } from 'node:child_process'; -import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; +import { readFileSync, writeFileSync, mkdtempSync, rmSync, readdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import openapiTS, { astToString } from 'openapi-typescript'; +import ts from 'typescript'; + +// Derive type roots from imports, rather than maintaining another schema list. +function schemaReferences(node, aliases) { + const roots = new Set(); + function visit(node) { + if (ts.isIndexedAccessTypeNode(node) && ts.isIndexedAccessTypeNode(node.objectType)) { + const inner = node.objectType; + if (ts.isTypeReferenceNode(inner.objectType) && ts.isIdentifier(inner.objectType.typeName) && + aliases.has(inner.objectType.typeName.text) && + ts.isLiteralTypeNode(inner.indexType) && inner.indexType.literal.text === 'schemas') { + if (!ts.isLiteralTypeNode(node.indexType) || !ts.isStringLiteral(node.indexType.literal)) { + throw new Error('Generated schemas require a literal type association'); + } + roots.add(node.indexType.literal.text); + } + } + ts.forEachChild(node, visit); + } + visit(node); + return roots; +} + +function usedSchemas(directory) { + const roots = new Set(); + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) { + if (entry.name !== '__tests__') for (const name of usedSchemas(path)) roots.add(name); + continue; + } + if (!/\.tsx?$/.test(entry.name) || /(?:\.test\.tsx?|generated-contract\.ts)$/.test(entry.name)) continue; + const source = ts.createSourceFile(path, readFileSync(path, 'utf8'), ts.ScriptTarget.Latest, true); + const aliases = new Set(); + for (const statement of source.statements) { + if (!ts.isImportDeclaration(statement) || !/generated-contract(?:\.js)?$/.test(statement.moduleSpecifier.text)) continue; + const bindings = statement.importClause?.namedBindings; + if (bindings && ts.isNamedImports(bindings)) { + for (const binding of bindings.elements) { + if ((binding.propertyName ?? binding.name).text === 'components') aliases.add(binding.name.text); + } + } + } + for (const name of schemaReferences(source, aliases)) roots.add(name); + } + return roots; +} + +function selectTypes(ast, roots) { + const declaration = ast.find(node => node.name?.escapedText === 'components'); + const schemas = declaration.members.find(node => node.name?.text === 'schemas'); + const available = new Map(schemas.type.members.map(node => [node.name.text, node])); + const selected = new Set(); + for (const name of roots) { + if (selected.has(name)) continue; + const node = available.get(name); + if (!node) throw new Error(`Consumer imports schema outside its HTTP contract: ${name}`); + selected.add(name); + for (const dependency of schemaReferences(node, new Set(['components']))) roots.add(dependency); + } + const members = [...selected].sort().map(name => available.get(name)); + const pruned = ts.factory.updatePropertySignature(schemas, schemas.modifiers, schemas.name, schemas.questionToken, + ts.factory.createTypeLiteralNode(members)); + return [ts.factory.updateInterfaceDeclaration(declaration, declaration.modifiers, declaration.name, + declaration.typeParameters, declaration.heritageClauses, [pruned])]; +} const check = process.argv.includes('--check'); const directory = mkdtempSync(join(tmpdir(), 'marreq-types-')); try { for (const component of ['frontend', 'mcp']) { const path = join(directory, `${component}.json`); // Inventory-only entries generate no invented types. Strict release/CI - // coverage is checked separately by `marreq-contracts validate --consumers`. + // coverage is checked separately by `marreq-contracts validate`. execFileSync('cargo', ['run', '--quiet', '--locked', '-p', 'marreq-contracts', '--', 'select', '--component', component, '--allow-unverified', '--output', path]); const spec = JSON.parse(readFileSync(path, 'utf8')); spec.paths = {}; // Clients currently import schemas, not generated SDK paths. - const ast = await openapiTS(spec); - const declarations = ast.filter(node => node.name?.escapedText === 'components'); + const ast = await openapiTS(spec, { alphabetize: true }); + const declarations = selectTypes(ast, usedSchemas(component === 'frontend' ? 'frontend/src' : 'mcp-server/src')); const result = '// Generated by scripts/generate-api-types.mjs; do not edit.\n' + astToString(declarations); const output = component === 'frontend' ? 'frontend/src/api/generated-contract.ts' : 'mcp-server/src/generated-contract.ts'; if (check) { diff --git a/tools/marreq-contracts/src/contract.rs b/tools/marreq-contracts/src/contract.rs index 1a829d30..56908314 100644 --- a/tools/marreq-contracts/src/contract.rs +++ b/tools/marreq-contracts/src/contract.rs @@ -324,10 +324,12 @@ pub fn compare(consumer: &Value, backend: &Value, variant: &str) -> Result Result> { v.as_array() .context("scope declarations must be arrays")? diff --git a/tools/marreq-contracts/src/coverage.rs b/tools/marreq-contracts/src/coverage.rs index 2a85d84f..e88e175d 100644 --- a/tools/marreq-contracts/src/coverage.rs +++ b/tools/marreq-contracts/src/coverage.rs @@ -514,10 +514,22 @@ fn analyze_file( let owner = enclosing(call) .child_by_field_name("name") .map(|n| text(n, source)); - if matches!( - owner, - Some("request" | "fetchJson" | "fetchBlob" | "fetchDownload") - ) { + let args = children(field(call, "arguments")?); + let forwarded = args.first().is_some_and(|arg| { + let expression = text(*arg, source); + (expression == "path" && owner != Some("request")) + || (expression == "url" + && owner == Some("request") + && !values(*arg, source, root, &BTreeMap::new(), 0) + .iter() + .any(|path| path.contains("/api/"))) + }); + if forwarded + && matches!( + owner, + Some("request" | "fetchJson" | "fetchBlob" | "fetchDownload") + ) + { continue; } } diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs index 036b940c..097917ea 100644 --- a/tools/marreq-contracts/tests/compatibility.rs +++ b/tools/marreq-contracts/tests/compatibility.rs @@ -54,6 +54,31 @@ fn additive_request_and_response() { response(&mut new)["properties"]["extra"] = json!({"type":"boolean"}); contract::compare(&spec(), &new, "server").unwrap(); } + +#[test] +fn multipart_required_field_changes_use_the_standards_engine() { + let mut old = spec(); + let schema = request(&mut old).clone(); + old["paths"]["/api/things"]["post"]["requestBody"]["content"] = + json!({"multipart/form-data":{"schema":schema}}); + let mut new = old.clone(); + let schema = &mut new["paths"]["/api/things"]["post"]["requestBody"]["content"]["multipart/form-data"] + ["schema"]; + schema["properties"]["new_field"] = json!({"type":"string"}); + schema["required"] = json!(["title", "new_field"]); + assert!(contract::compare(&old, &new, "server").is_err()); +} + +#[test] +fn file_download_media_type_removal_fails() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = + json!({"application/pdf":{"schema":{"type":"string","format":"binary"}}}); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = + json!({"application/json":{"schema":{"type":"object"}}}); + assert!(contract::compare(&old, &new, "server").is_err()); +} #[test] fn response_enum_extension() { let mut new = spec(); @@ -216,6 +241,44 @@ fn incompatible_backend_only_update() { let dir = tempfile::tempdir().unwrap(); assert!(artifact::verify(&plan(dir.path(), "0.2.4", "0.3.0", true), None).is_err()); } + +#[test] +fn compatible_frontend_only_update() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let bundle_path = dir.path().join("frontend.json"); + let mut bundle: Bundle = serde_json::from_value(contract::load(&bundle_path).unwrap()).unwrap(); + bundle.version = "0.2.2".parse().unwrap(); + contract::write(&bundle_path, &bundle).unwrap(); + let mut deployment: Plan = serde_json::from_value(contract::load(&path).unwrap()).unwrap(); + deployment.frontend.version = bundle.version; + deployment.frontend.sha256 = artifact::hash(&std::fs::read(bundle_path).unwrap()); + contract::write(&path, &deployment).unwrap(); + assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); +} + +#[test] +fn changed_authorization_guard_fails() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("project-member"); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("instance-admin"); + assert!(contract::compare(&old, &new, "server").is_err()); +} + +#[test] +fn shared_transport_does_not_hide_concrete_calls() { + let ops = contract::operations(&spec(), None).unwrap(); + for source in [ + "function fetchJson(path:string){return fetch('/api/missing');}", + "class Client {request(path:string){const url='/api/missing';return fetch(url);}}", + ] { + assert!( + coverage::analyze(source, false, &ops, true).is_err(), + "{source}" + ); + } +} #[test] fn missing_historical_artifact() { let dir = tempfile::tempdir().unwrap(); From 755bd9cb20e08dbff4d21394094965e7307b7695 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 3 Nov 2026 15:36:25 +0100 Subject: [PATCH 05/18] feat(contracts): audit session, cache, and attachment wire contracts --- docs/developer/http-api-contract.md | 32 +- docs/developer/openapi.yaml | 1176 ++++++++++++++--- docs/releases.md | 15 +- frontend/src/api/cache.ts | 20 +- frontend/src/api/generated-contract.ts | 386 +----- frontend/src/api/types.ts | 50 +- marreq-cloud/tests/api_contract_inventory.rs | 108 +- mcp-server/dist/client.js | 104 +- mcp-server/dist/index.js | 98 +- mcp-server/src/generated-contract.ts | 272 ---- scripts/smoke-api-consumers.mjs | 11 +- tools/marreq-contracts/tests/compatibility.rs | 19 + 12 files changed, 1257 insertions(+), 1034 deletions(-) diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index c0763b70..be84b37e 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -236,11 +236,15 @@ audit and cannot be used in a releasable consumer bundle. Unconsumed backend endpoints are outside this contract; adding/changing them does not itself block an existing consumer. -**The refactor remains draft.** The earlier 245-operation global completeness gate -was removed, but most remaining gaps are genuinely consumed APIs. Completing their -wire schemas, binding more producer DTO serialization to those schemas, and -retaining actual historical artifact evidence are still release blockers. A -passing document validator alone is not a compatibility guarantee. +**PR #424 remains draft.** Discovery currently selects 195 frontend operations and +79 MCP operations: 217 distinct dependencies, of which 59 have audited wire +contracts and 158 remain unverified. The audited additions cover cache +administration, session/profile/identity flows, connected grants, multipart +attachments, file downloads and storage quotas, alongside the existing metadata, +OAuth, Cloud account and use-case contracts. These counts describe the current +source, not a separate inventory to maintain. Complete the remaining consumed +wire audits and producer regression protection before treating this as merge-ready. +A passing document validator alone is not a compatibility guarantee. ### Architecture and rationale @@ -269,7 +273,8 @@ passing document validator alone is not a compatibility guarantee. Server/Cloud registration. Extra backend routes are permitted. Four public account routes are Cloud-only; shared deployment capabilities remain runtime behavior rather than invented Server/Cloud route differences. -- Standard `openapi-typescript` emits only schemas reachable by each consumer. +- Standard `openapi-typescript` and the TypeScript compiler emit only schemas + imported by each consumer and their type dependencies, in sorted order. Committed declarations keep Rust/global OpenAPI out of frontend/MCP Docker builds. Each consumer owns its minimal build input; the duplicate global type snapshots were replaced by independently selected declarations. @@ -280,12 +285,16 @@ existing requirements-compliance Python script is retained. ### Developer commands -Install the pinned upstream engine once (`scripts/install-oasdiff.sh`; CI does -this automatically), add its destination to PATH, or set `OASDIFF` to that binary. +Workspace tests, including coverage, require the pinned upstream engine. Install +it once and expose its destination on PATH (or set `OASDIFF` to that binary). +Both contract CI and workspace coverage CI use the same installer. The installer currently handles Linux x86_64; other platforms may install the same upstream version themselves. No contract command downloads refs or schemas. ```sh +scripts/install-oasdiff.sh +export PATH="$HOME/.cache/marreq-contracts:$PATH" +npm ci # pinned generator and TypeScript compiler cargo test --locked -p marreq-contracts cargo run --locked -p marreq-contracts -- validate cargo run --locked -p marreq-contracts -- coverage --component frontend @@ -323,5 +332,8 @@ The CLI reports exact operation and call-site counts. Keep the PR draft while an required operation is unverified, a producer behavior lacks adequate contract protection, or a required check fails. Hand-maintained schemas still need broader Rust DTO/serialization verification; compiled route presence alone is insufficient. -The remaining upstream Rust advisory is tracked in -[#425](https://github.com/MarreqSW/Marreq/issues/425); the security gate is unchanged. +The advisory policy is inherited unchanged from `main`, including its +`crypto-bigint` exception tracked in [#425](https://github.com/MarreqSW/Marreq/issues/425). +The newly reported `calamine` advisory RUSTSEC-2026-0436 is fixed with the targeted +0.38.0 update; no additional ignores are introduced. MCP production audit still +reports a moderate advisory below the existing high-severity failure threshold. diff --git a/docs/developer/openapi.yaml b/docs/developer/openapi.yaml index 6f23216b..9aa3d5e6 100644 --- a/docs/developer/openapi.yaml +++ b/docs/developer/openapi.yaml @@ -5,6 +5,114 @@ info: {title: Marreq HTTP API, version: 0.1.0, description: Consumer-facing HTTP servers: - {url: /} paths: + /api/cache/stats: + get: + operationId: cache_stats + description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session + administrator only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CacheStats" + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: Not an administrator, or CSRF check failed; body may be framework-generated. + "500": + $ref: "#/components/responses/ApiError" + /api/cache/recommendations: + get: + operationId: cache_recommendations + description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session + administrator only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CacheRecommendations" + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: Not an administrator, or CSRF check failed; body may be framework-generated. + "500": + $ref: "#/components/responses/ApiError" + /api/cache/clear: + post: + operationId: cache_clear + description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session + administrator only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CacheActionResult" + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: Not an administrator, or CSRF check failed; body may be framework-generated. + "500": + $ref: "#/components/responses/ApiError" + x-marreq-csrf: session-csrf + /api/cache/cleanup: + post: + operationId: cache_cleanup + description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session + administrator only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CacheCleanupResult" + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: Not an administrator, or CSRF check failed; body may be framework-generated. + "500": + $ref: "#/components/responses/ApiError" + x-marreq-csrf: session-csrf + /api/cache/reset-counters: + post: + operationId: cache_reset_counters + description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session + administrator only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/CacheActionResult" + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: Not an administrator, or CSRF check failed; body may be framework-generated. + "500": + $ref: "#/components/responses/ApiError" + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{id}/activity: get: operationId: activity_requirement_activity_by_project @@ -93,121 +201,394 @@ paths: get: operationId: attachments_list parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + - in: path + name: project_id + required: true + schema: + type: integer + - in: query + name: entity_type + required: true + schema: + type: string + enum: + - requirement + - verification + - in: query + name: entity_id + required: true + schema: + type: integer + responses: + "200": + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AttachmentView" + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "422": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-requirements-and-verifications-read + x-marreq-bearer-scopes: + - requirements:read + - verifications:read post: operationId: attachments_upload parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - in: path + name: project_id + required: true + schema: + type: integer responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "201": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AttachmentView" + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "413": + description: Upload exceeds the file or project quota, or Rocket's request limit. The handler returns + ApiError JSON; Rocket can reject an oversized form before the handler with a framework-generated body. + "415": + $ref: "#/components/responses/ApiError" + "422": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + entity_type: + type: string + enum: + - requirement + - verification + entity_id: + type: integer + required: + - file + - entity_type + - entity_id + additionalProperties: true /api/projects/{project_id}/attachments/{attachment_id}/download: get: operationId: attachments_download parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: attachment_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + - in: path + name: project_id + required: true + schema: + type: integer + - in: path + name: attachment_id + required: true + schema: + type: integer + responses: + "200": + description: Stored file served as a download using its detected media type. + headers: + Content-Disposition: + required: true + schema: + type: string + X-Content-Type-Options: + required: true + schema: + type: string + const: nosniff + Content-Security-Policy: + required: true + schema: + type: string + const: default-src 'none'; sandbox + Cache-Control: + required: true + schema: + type: string + const: private, no-cache + content: + "*/*": + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied /api/projects/{project_id}/storage: get: operationId: attachments_get_storage parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - in: path + name: project_id + required: true + schema: + type: integer responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AttachmentStorage" + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied /api/projects/{project_id}/storage/quota: put: operationId: attachments_set_quota parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - in: path + name: project_id + required: true + schema: + type: integer responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AttachmentStorage" + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "422": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin;not-archived + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + quota_mb: + anyOf: + - type: integer + minimum: 1 + maximum: 1073741824 + - type: "null" + additionalProperties: false /api/projects/{project_id}/baselines/{baseline_id}/attachments: get: operationId: attachments_list_for_baseline parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + - in: path + name: project_id + required: true + schema: + type: integer + - in: path + name: baseline_id + required: true + schema: + type: integer + responses: + "200": + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AttachmentView" + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read + x-marreq-bearer-scopes: + - baselines:read /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: get: operationId: attachments_download_for_baseline parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} - - in: path - name: attachment_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + - in: path + name: project_id + required: true + schema: + type: integer + - in: path + name: baseline_id + required: true + schema: + type: integer + - in: path + name: attachment_id + required: true + schema: + type: integer + responses: + "200": + description: Stored file served as a download using its detected media type. + headers: + Content-Disposition: + required: true + schema: + type: string + X-Content-Type-Options: + required: true + schema: + type: string + const: nosniff + Content-Security-Policy: + required: true + schema: + type: string + const: default-src 'none'; sandbox + Cache-Control: + required: true + schema: + type: string + const: private, no-cache + content: + "*/*": + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read + x-marreq-bearer-scopes: + - baselines:read /api/auth/providers: get: operationId: auth_auth_providers @@ -223,38 +604,94 @@ paths: get: operationId: auth_auth_external_start parameters: - - in: path - name: provider - required: true - schema: {type: string} + - in: path + name: provider + required: true + schema: + type: string + - in: query + name: return_to + schema: + type: string responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. + "303": + description: Redirect to the provider or validated local return path. + headers: + Location: + required: true + schema: + type: string + "400": + $ref: "#/components/responses/ApiError" + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. security: [] /api/auth/external/{provider}/callback: get: operationId: auth_auth_external_callback parameters: - - in: path - name: provider - required: true - schema: {type: string} + - in: path + name: provider + required: true + schema: + type: string + - in: query + name: code + schema: + type: string + - in: query + name: state + schema: + type: string + - in: query + name: error + schema: + type: string responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. + "303": + description: Redirect to the provider or validated local return path. + headers: + Location: + required: true + schema: + type: string + "400": + $ref: "#/components/responses/ApiError" + "401": + $ref: "#/components/responses/ApiError" + "404": + $ref: "#/components/responses/ApiError" + "409": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. security: [] + x-marreq-authorization: single-use-oauth-transaction;link-requires-original-session /api/auth/identities: get: operationId: auth_auth_identities parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthIdentities" + "401": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. + security: + - cookieAuth: [] /api/auth/me: get: operationId: auth_auth_me @@ -278,11 +715,38 @@ paths: operationId: auth_auth_update_me parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/entities_User_response" + "400": + $ref: "#/components/responses/ApiError" + "401": + $ref: "#/components/responses/ApiError" + "403": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "404": + $ref: "#/components/responses/ApiError" + "409": + $ref: "#/components/responses/ApiError" + "422": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and services/user_service.rs. Own profile only. Email changes require the + current password when configured; Cloud rejects email changes. Other profile changes work in both + variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthProfileUpdate" + x-marreq-csrf: session-csrf /api/admin/backup/info: get: operationId: backup_info @@ -3666,17 +4130,29 @@ paths: delete: operationId: auth_auth_identity_delete parameters: - - in: path - name: identity_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + - in: path + name: identity_id + required: true + schema: + type: integer + responses: + "204": + description: Owned identity removed; no body. + "400": + $ref: "#/components/responses/ApiError" + "401": + $ref: "#/components/responses/ApiError" + "403": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. + security: + - cookieAuth: [] + x-marreq-csrf: session-csrf /api/auth/register: post: operationId: auth_public_auth_register @@ -3780,37 +4256,89 @@ paths: delete: operationId: attachments_delete parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: attachment_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] + - in: path + name: project_id + required: true + schema: + type: integer + - in: path + name: attachment_id + required: true + schema: + type: integer + responses: + "204": + description: Attachment deleted; empty body. Baseline retention can keep the stored bytes. + "400": + $ref: "#/components/responses/ApiError" + "401": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "403": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + "503": + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or + framework-generated. + description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same + routes and wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied + x-marreq-csrf: session-csrf /api/auth/external/{provider}/link: post: operationId: auth_auth_external_link_start parameters: - - in: path - name: provider - required: true - schema: {type: string} + - in: path + name: provider + required: true + schema: + type: string responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + authorization_url: + type: string + required: + - authorization_url + additionalProperties: true + "400": + $ref: "#/components/responses/ApiError" + "401": + $ref: "#/components/responses/ApiError" + "403": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "404": + $ref: "#/components/responses/ApiError" + "422": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: + schema: + description: Any JSON value is accepted; an object return_to string overrides the default /settings/account. + properties: + return_to: + type: string + x-marreq-csrf: session-csrf /api/auth/verify-email: get: operationId: auth_public_auth_verify_email @@ -3909,38 +4437,107 @@ paths: operationId: auth_auth_change_password parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AuthStatus" + "400": + $ref: "#/components/responses/ApiError" + "401": + $ref: "#/components/responses/ApiError" + "403": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "422": + description: Authentication or CSRF failure; body may be framework-generated or empty. + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in + Server and Cloud. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AuthChangePassword" + x-marreq-csrf: session-csrf /api/oauth/grants: get: operationId: oauth_grants parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Connected applications + content: + application/json: + schema: + type: object + required: + - grants + properties: + grants: + type: array + items: + type: object + required: + - id + - application_name + - scopes + - created_at + - last_used_at + properties: + id: + type: integer + application_name: + type: string + scopes: + type: array + items: + type: string + created_at: + type: string + last_used_at: + type: + - string + - "null" + additionalProperties: true + additionalProperties: true + "401": + description: Missing or expired session; guard response may be empty. + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants + only, identical in Server and Cloud; no Bearer authentication. + security: + - cookieAuth: [] /api/oauth/grants/{id}: delete: operationId: oauth_revoke_grant parameters: - - in: path - name: id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + - in: path + name: id + required: true + schema: + type: integer + responses: + "204": + description: Owned grant revoked; empty body. + "401": + description: Missing or expired session; guard response may be empty. + "403": + description: CSRF failure; body may be framework-generated. + "404": + $ref: "#/components/responses/ApiError" + "500": + $ref: "#/components/responses/ApiError" + description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants + only, identical in Server and Cloud; no Bearer authentication. + security: + - cookieAuth: [] + x-marreq-csrf: session-csrf /api/dashboard: get: operationId: dashboard_dashboard_json @@ -3993,6 +4590,239 @@ components: mcpServiceAuth: {type: apiKey, in: header, name: X-Marreq-Mcp-Audit-Secret, description: Private service-to-service audit secret; never browser or delegated caller configuration.} schemas: + AttachmentView: + type: object + properties: + id: + type: integer + entity_type: + type: string + enum: + - requirement + - verification + entity_id: + type: integer + filename: + type: string + content_type: + type: string + size_bytes: + type: integer + uploaded_by: + type: + - integer + - "null" + uploaded_by_name: + type: + - string + - "null" + created_at: + type: string + deleted: + type: boolean + required: + - id + - entity_type + - entity_id + - filename + - content_type + - size_bytes + - uploaded_by + - uploaded_by_name + - created_at + - deleted + additionalProperties: true + AttachmentStorage: + type: object + properties: + used_bytes: + type: integer + quota_bytes: + type: integer + quota_is_default: + type: boolean + default_quota_bytes: + type: integer + retained_by_baselines_bytes: + type: integer + max_file_bytes: + type: integer + allowed_extensions: + type: array + items: + type: string + required: + - used_bytes + - quota_bytes + - quota_is_default + - default_quota_bytes + - retained_by_baselines_bytes + - max_file_bytes + - allowed_extensions + additionalProperties: true + AuthProfileUpdate: + type: object + properties: + name: + type: string + email: + type: string + current_password: + type: + - string + - "null" + required: + - name + - email + additionalProperties: true + AuthIdentities: + type: object + properties: + password_configured: + type: boolean + identities: + type: array + items: + type: object + properties: + id: + type: integer + provider: + type: string + created_at: + type: string + last_login_at: + type: + - string + - "null" + required: + - id + - provider + - created_at + - last_login_at + additionalProperties: true + required: + - password_configured + - identities + additionalProperties: true + AuthChangePassword: + type: object + properties: + current_password: + type: string + new_password: + type: string + confirm_password: + type: string + required: + - current_password + - new_password + - confirm_password + additionalProperties: true + AuthStatus: + type: object + properties: + status: + type: string + const: ok + required: + - status + additionalProperties: true + CacheStats: + type: object + properties: + total_entries: + type: integer + minimum: 0 + active_entries: + type: integer + minimum: 0 + expired_entries: + type: integer + minimum: 0 + hits: + type: integer + minimum: 0 + misses: + type: integer + minimum: 0 + total_requests: + type: integer + minimum: 0 + average_access_time_ns: + type: integer + minimum: 0 + total_access_time_ns: + type: integer + minimum: 0 + cache_size_bytes: + type: integer + minimum: 0 + hit_rate: + type: number + miss_rate: + type: number + last_cleanup: + type: string + format: date-time + required: + - total_entries + - active_entries + - expired_entries + - hits + - misses + - total_requests + - average_access_time_ns + - total_access_time_ns + - cache_size_bytes + - hit_rate + - miss_rate + - last_cleanup + additionalProperties: true + CacheRecommendations: + type: object + properties: + recommendations: + type: array + items: + type: string + priority: + type: string + enum: + - high + - medium + - low + analysis_timestamp: + type: string + format: date-time + required: + - recommendations + - priority + - analysis_timestamp + additionalProperties: true + CacheActionResult: + type: object + properties: + message: + type: string + timestamp: + type: string + format: date-time + required: + - message + - timestamp + additionalProperties: true + CacheCleanupResult: + allOf: + - $ref: "#/components/schemas/CacheActionResult" + - type: object + properties: + cleaned_entries: + type: integer + minimum: 0 + required: + - cleaned_entries + additionalProperties: true external_ProviderDiscovery_response: type: object properties: diff --git a/docs/releases.md b/docs/releases.md index e749832b..cdcf1714 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -136,8 +136,9 @@ Optional request fields and additional response fields are compatible where the old contract accepts them. Removed guaranteed response properties, new required request properties, narrowed request enums, widened closed response enums, incompatible types/authentication, or missing required operations fail. Per-operation -Bearer scopes and stable CSRF policy IDs are checked separately; unused global -security schemes and exact global status/header set equality do not couple APIs. +Bearer scopes, authorization policies and stable CSRF policy IDs are checked +separately; unused global security schemes and exact global status/header set +equality do not couple APIs. The pinned standards engine decides ordinary OpenAPI changes, not homegrown rules. Missing evidence, altered hashes, mismatched source/version, invalid policy, unsupported semantics and uncertain required operations exit nonzero with JSON @@ -150,7 +151,15 @@ candidate, and verifies its version, source and bundle hash against the plan bef any image publisher runs. The release gate uses the **actual artifact combination**; it does not require the latest checkout's unchanged consumers. The PR gate instead checks all calls in current source. No gate requires coverage of unconsumed routes. -No global release migration or production deployment is performed here. +To provision `MARREQ_RELEASE_CONTRACTS_SHA`, export each bundle from its actual +clean release source, retain the files together, and write `deployment.json` with +their printed SHA-256 values, versions and source SHAs. Run `verify --manifest` +locally, commit that plan and its bundles, then set the repository Actions variable +to that immutable commit SHA before pushing the component tag. For a candidate, +the plan must reference the exact tagged source-exported bundle. Do not reuse a +previous plan unchanged or synthesize a manifest from current `main`; missing +historical evidence deliberately prevents publication. No global release migration +or production deployment is performed here. The PR remains draft until required consumer schema gaps and security failures are resolved; a document-only check is not a merge/release-readiness assertion. diff --git a/frontend/src/api/cache.ts b/frontend/src/api/cache.ts index ce4a68e1..a5ba7e4d 100644 --- a/frontend/src/api/cache.ts +++ b/frontend/src/api/cache.ts @@ -41,24 +41,26 @@ export async function getCacheRecommendations(): Promise { return fetchJson('/api/cache/recommendations'); } -function cachePost(path: string, csrfToken: string): Promise { - return fetchJson(path, { +/** Drops every cached entry; the next requests rebuild it from the database. */ +export function clearCache(csrfToken: string): Promise { + return fetchJson('/api/cache/clear', { method: 'POST', headers: { 'X-CSRF-Token': csrfToken }, }); } -/** Drops every cached entry; the next requests rebuild it from the database. */ -export function clearCache(csrfToken: string): Promise { - return cachePost('/api/cache/clear', csrfToken); -} - /** Drops only the expired entries. */ export function cleanupCache(csrfToken: string): Promise { - return cachePost('/api/cache/cleanup', csrfToken); + return fetchJson('/api/cache/cleanup', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken }, + }); } /** Zeroes the hit/miss counters without touching the cached entries. */ export function resetCacheCounters(csrfToken: string): Promise { - return cachePost('/api/cache/reset-counters', csrfToken); + return fetchJson('/api/cache/reset-counters', { + method: 'POST', + headers: { 'X-CSRF-Token': csrfToken }, + }); } diff --git a/frontend/src/api/generated-contract.ts b/frontend/src/api/generated-contract.ts index 13b8e14f..69dd4ba1 100644 --- a/frontend/src/api/generated-contract.ts +++ b/frontend/src/api/generated-contract.ts @@ -1,10 +1,56 @@ // Generated by scripts/generate-api-types.mjs; do not edit. export interface components { schemas: { - ApiError: { - error: string; - message: string; - status: number; + AttachmentStorage: { + allowed_extensions: string[]; + default_quota_bytes: number; + max_file_bytes: number; + quota_bytes: number; + quota_is_default: boolean; + retained_by_baselines_bytes: number; + used_bytes: number; + } & { + [key: string]: unknown; + }; + AttachmentView: { + content_type: string; + created_at: string; + deleted: boolean; + entity_id: number; + /** @enum {string} */ + entity_type: "requirement" | "verification"; + filename: string; + id: number; + size_bytes: number; + uploaded_by: number | null; + uploaded_by_name: string | null; + } & { + [key: string]: unknown; + }; + AuthChangePassword: { + confirm_password: string; + current_password: string; + new_password: string; + } & { + [key: string]: unknown; + }; + AuthIdentities: { + identities: ({ + created_at: string; + id: number; + last_login_at: string | null; + provider: string; + } & { + [key: string]: unknown; + })[]; + password_configured: boolean; + } & { + [key: string]: unknown; + }; + AuthProfileUpdate: { + current_password?: string | null; + email: string; + name: string; } & { [key: string]: unknown; }; @@ -34,238 +80,6 @@ export interface components { } & { [key: string]: unknown; }; - UseCaseBranch: { - end: components["schemas"]["UseCaseBranchEnd"]; - from_step: string; - id: string; - /** @enum {string} */ - kind: "alternative" | "exception"; - steps?: components["schemas"]["UseCaseElement"][]; - title: string; - }; - UseCaseBranchEnd: { - step: string; - /** @constant */ - type: "rejoin"; - } | { - /** @enum {string} */ - outcome: "success" | "failure"; - /** @constant */ - type: "end"; - }; - UseCaseBranchResponse: { - end: components["schemas"]["UseCaseBranchEnd"]; - from_step: string; - id: string; - /** @enum {string} */ - kind: "alternative" | "exception"; - steps: components["schemas"]["UseCaseElement"][]; - title: string; - }; - UseCaseDependents: "cascade" | ({ - rebranch_to: string; - } & { - [key: string]: unknown; - }); - UseCaseDiff: { - elements: components["schemas"]["UseCaseElementDiff"][]; - from: components["schemas"]["UseCaseDiffEnd"] | null; - header: { - [key: string]: { - new: string | null | string[]; - old: string | null | string[]; - } & { - [key: string]: unknown; - }; - }; - summary: { - added: number; - edited: number; - moved: number; - rebranched: number; - removed: number; - } & { - [key: string]: unknown; - }; - to: components["schemas"]["UseCaseDiffEnd"]; - } & { - [key: string]: unknown; - }; - UseCaseDiffEnd: { - draft_revision: number | null; - version_id: number | null; - version_number: number | null; - } & { - [key: string]: unknown; - }; - UseCaseDiffSide: { - branch_id?: string; - end?: components["schemas"]["UseCaseBranchEnd"]; - from_step?: string; - /** @enum {string} */ - kind?: "alternative" | "exception"; - label: string | null; - /** @enum {string} */ - list: "preconditions" | "main" | "postconditions" | "branch" | "branches"; - text: string; - } & { - [key: string]: unknown; - }; - UseCaseDocument: { - branches?: components["schemas"]["UseCaseBranch"][]; - main?: components["schemas"]["UseCaseElement"][]; - postconditions?: components["schemas"]["UseCaseElement"][]; - preconditions?: components["schemas"]["UseCaseElement"][]; - primary_actor?: string | null; - /** @constant */ - schema?: "marreq.use-case.v1"; - secondary_actors?: string[]; - summary?: string | null; - title: string; - }; - UseCaseDocumentResponse: { - branches: components["schemas"]["UseCaseBranchResponse"][]; - main: components["schemas"]["UseCaseElement"][]; - postconditions: components["schemas"]["UseCaseElement"][]; - preconditions: components["schemas"]["UseCaseElement"][]; - primary_actor: string | null; - /** @constant */ - schema: "marreq.use-case.v1"; - secondary_actors: string[]; - summary: string | null; - title: string; - }; - UseCaseEffect: ({ - from: string; - id: string; - to: string; - /** @constant */ - type: "relabelled"; - } & { - [key: string]: unknown; - }) | ({ - id: string; - /** @constant */ - type: "removed"; - } & { - [key: string]: unknown; - }) | ({ - from_step: string; - id: string; - /** @constant */ - type: "rebranched"; - } & { - [key: string]: unknown; - }) | ({ - id: string; - /** @constant */ - type: "created"; - } & { - [key: string]: unknown; - }); - UseCaseElement: { - id: string; - text: string; - }; - UseCaseElementDiff: { - changes: ("added" | "removed" | "edited" | "moved" | "rebranched")[]; - id: string; - /** @enum {string} */ - kind: "precondition" | "postcondition" | "step" | "branch"; - new: components["schemas"]["UseCaseDiffSide"] | null; - old: components["schemas"]["UseCaseDiffSide"] | null; - text_diff?: ({ - /** @enum {string} */ - op: "equal" | "delete" | "insert"; - text: string; - } & { - [key: string]: unknown; - })[]; - } & { - [key: string]: unknown; - }; - UseCaseListRef: ("preconditions" | "main" | "postconditions") | ({ - branch: string; - } & { - [key: string]: unknown; - }); - UseCaseNewBranch: { - end: components["schemas"]["UseCaseBranchEnd"]; - from_step: string; - id?: string | null; - /** @enum {string} */ - kind: "alternative" | "exception"; - steps?: components["schemas"]["UseCaseNewElement"][]; - title: string; - }; - UseCaseNewElement: { - id?: string | null; - text: string; - }; - UseCaseOperation: { - /** @constant */ - op: "set_header"; - primary_actor?: string | null; - secondary_actors?: string[] | null; - summary?: string | null; - title?: string | null; - } | { - after?: string | null; - element: components["schemas"]["UseCaseNewElement"]; - list: components["schemas"]["UseCaseListRef"]; - /** @constant */ - op: "add_element"; - } | { - id: string; - /** @constant */ - op: "update_text"; - text: string; - } | { - after?: string | null; - id: string; - list?: components["schemas"]["UseCaseListRef"] | null; - /** @constant */ - op: "move_element"; - } | { - dependents?: components["schemas"]["UseCaseDependents"] | null; - id: string; - /** @constant */ - op: "remove_element"; - } | { - branch: components["schemas"]["UseCaseNewBranch"]; - /** @constant */ - op: "add_branch"; - } | { - end?: components["schemas"]["UseCaseBranchEnd"] | null; - from_step?: string | null; - id: string; - kind?: ("alternative" | "exception") | null; - /** @constant */ - op: "update_branch"; - title?: string | null; - } | { - id: string; - /** @constant */ - op: "remove_branch"; - }; - UseCasePublishResponse: { - removed_links: components["schemas"]["use_cases_LinkView_response"][]; - version: components["schemas"]["use_cases_VersionView_response"]; - } & { - [key: string]: unknown; - }; - entities_User_response: { - creation_date: string; - email: string; - email_verified: boolean; - id: number; - is_admin: boolean; - last_login: string; - name: string; - username: string; - } & { - [key: string]: unknown; - }; external_ProviderDiscovery_response: { external: components["schemas"]["external_PublicAuthProvider_response"][]; password_enabled: boolean; @@ -292,27 +106,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_ClearSuspectResponse_response: { - cleared: boolean; - status: string; - } & { - [key: string]: unknown; - }; - use_cases_DraftChangeView_response: { - base_version_id: number | null; - document: components["schemas"]["UseCaseDocumentResponse"]; - effects: components["schemas"]["UseCaseEffect"][]; - labels: { - [key: string]: string; - }; - revision: number; - updated_at: string; - updated_by: number | null; - updated_by_name: string | null; - use_case_id: number; - } & { - [key: string]: unknown; - }; use_cases_DraftSummary_response: { base_version_id: number | null; revision: number; @@ -322,20 +115,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_DraftView_response: { - base_version_id: number | null; - document: components["schemas"]["UseCaseDocumentResponse"]; - labels: { - [key: string]: string; - }; - revision: number; - updated_at: string; - updated_by: number | null; - updated_by_name: string | null; - use_case_id: number; - } & { - [key: string]: unknown; - }; use_cases_LinkView_response: { created_at: string; created_by: number | null; @@ -357,22 +136,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_NextCode_response: { - reference_code: string; - } & { - [key: string]: unknown; - }; - use_cases_PutDraftRequest_request: { - base_revision?: number | null; - document: components["schemas"]["UseCaseDocument"]; - } & { - [key: string]: unknown; - }; - use_cases_SetApprovalRequest_request: { - state: string; - } & { - [key: string]: unknown; - }; use_cases_UseCaseCommentView_response: { author_id: number; author_name: string; @@ -386,19 +149,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_UseCaseDetail_response: { - created_at: string; - created_by: number | null; - current_version: components["schemas"]["use_cases_VersionView_response"] | null; - draft: components["schemas"]["use_cases_DraftSummary_response"] | null; - id: number; - project_id: number; - reference_code: string; - title: string; - updated_at: string; - } & { - [key: string]: unknown; - }; use_cases_UseCaseRef_response: { element_id: string | null; element_label: string | null; @@ -444,41 +194,5 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_VersionView_response: { - approval_state: string; - approved_at: string | null; - approved_by: number | null; - change_note: string | null; - created_at: string; - created_by: number | null; - created_by_name: string | null; - document: components["schemas"]["UseCaseDocumentResponse"]; - id: number; - labels: { - [key: string]: string; - }; - primary_actor: string | null; - reviewed_at: string | null; - reviewed_by: number | null; - title: string; - version_number: number; - } & { - [key: string]: unknown; - }; - }; - responses: { - /** @description API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. */ - ApiError: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ApiError"]; - }; - }; }; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; } diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 32c8be4f..e01210dc 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -192,17 +192,9 @@ export type DeploymentInfo = ApiComponents['schemas']['DeploymentInfo']; export type AuthProviderDiscovery = ApiComponents['schemas']['external_ProviderDiscovery_response']; -export interface ConnectedIdentity { - id: number; - provider: string; - created_at: string; - last_login_at: string | null; -} +export type ConnectedIdentity = ConnectedIdentities['identities'][number]; -export interface ConnectedIdentities { - password_configured: boolean; - identities: ConnectedIdentity[]; -} +export type ConnectedIdentities = ApiComponents['schemas']['AuthIdentities']; export interface ConnectedApplication { id: number; @@ -236,11 +228,7 @@ export type ResetPasswordBody = { new_password: string; }; -export type ChangePasswordBody = { - current_password: string; - new_password: string; - confirm_password: string; -}; +export type ChangePasswordBody = ApiComponents['schemas']['AuthChangePassword']; /** `GET /api/projects/:pid/requirements/:id` โ€” requirement fields flattened + trace_summary. */ export interface RequirementVersionLink { @@ -608,12 +596,7 @@ export interface User { } /** Body for `PUT /api/auth/me`: the signed-in user's own name and email. */ -export interface ProfileUpdateRequest { - name: string; - email: string; - /** Required when the email changes and the account has a password. */ - current_password?: string; -} +export type ProfileUpdateRequest = ApiComponents['schemas']['AuthProfileUpdate']; /** Body for `PUT /api/users/:id` (admin only). */ export interface UserUpdateRequest { @@ -827,31 +810,10 @@ export interface ProjectUpdateBody { export type AttachmentEntityType = 'requirement' | 'verification'; /** A file attached to a requirement or verification (`GET /projects/{id}/attachments`). */ -export interface Attachment { - id: number; - entity_type: AttachmentEntityType; - entity_id: number; - filename: string; - content_type: string; - size_bytes: number; - uploaded_by: number | null; - uploaded_by_name: string | null; - created_at: string; - /** Only true in baseline listings, for files deleted after the baseline. */ - deleted: boolean; -} +export type Attachment = ApiComponents['schemas']['AttachmentView']; /** `GET /projects/{id}/storage`: attachment storage used by a project and the limits. */ -export interface ProjectStorage { - used_bytes: number; - quota_bytes: number; - quota_is_default: boolean; - default_quota_bytes: number; - /** Part of `used_bytes` taken by deleted files that baselines still keep. */ - retained_by_baselines_bytes: number; - max_file_bytes: number; - allowed_extensions: string[]; -} +export type ProjectStorage = ApiComponents['schemas']['AttachmentStorage']; /** `GET|PUT /api/projects/:pid/verifications/:vid/control` (issue #353). */ export interface VerificationControl { diff --git a/marreq-cloud/tests/api_contract_inventory.rs b/marreq-cloud/tests/api_contract_inventory.rs index d1dbac2a..1b989a0f 100644 --- a/marreq-cloud/tests/api_contract_inventory.rs +++ b/marreq-cloud/tests/api_contract_inventory.rs @@ -3,12 +3,16 @@ //! Additional, unconsumed backend routes deliberately do not fail this check. use std::collections::BTreeSet; -#[test] -fn consumed_contract_routes_exist_in_server_and_cloud() { - let spec = marreq_contracts::contract::load( +fn spec() -> serde_json::Value { + marreq_contracts::contract::load( &std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../docs/developer/openapi.yaml"), ) - .unwrap(); + .unwrap() +} + +#[test] +fn consumed_contract_routes_exist_in_server_and_cloud() { + let spec = spec(); for (variant, extra) in [ ("server", Vec::new()), ("cloud", marreq_cloud::routes::routes()), @@ -49,3 +53,99 @@ fn consumed_contract_routes_exist_in_server_and_cloud() { } } } + +#[test] +fn audited_cache_models_match_the_wire_schemas() { + let spec = spec(); + let cache = marreq_core::repository::cache::Cache::new(60); + for (name, value) in [ + ("CacheStats", serde_json::to_value(cache.stats()).unwrap()), + ("CacheRecommendations", cache.get_recommendations()), + ] { + let schema = &spec["components"]["schemas"][name]; + marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + for property in schema["required"].as_array().unwrap() { + let mut broken = value.clone(); + broken + .as_object_mut() + .unwrap() + .remove(property.as_str().unwrap()); + assert!(marreq_contracts::contract::validate_instance(&spec, schema, &broken).is_err()); + } + } +} + +#[test] +fn audited_auth_requests_deserialize_as_the_real_rust_forms() { + use marreq_core::models::{ChangePasswordForm, ProfileUpdate}; + use serde_json::json; + let spec = spec(); + for (name, value) in [ + ( + "AuthProfileUpdate", + json!({"name":"Manager","email":"manager@example.test"}), + ), + ( + "AuthProfileUpdate", + json!({"name":"Manager","email":"manager@example.test","current_password":null}), + ), + ( + "AuthChangePassword", + json!({"current_password":"old","new_password":"new","confirm_password":"new"}), + ), + ] { + marreq_contracts::contract::validate_instance( + &spec, + &spec["components"]["schemas"][name], + &value, + ) + .unwrap(); + if name == "AuthProfileUpdate" { + serde_json::from_value::(value).unwrap(); + } else { + serde_json::from_value::(value).unwrap(); + } + } +} + +#[test] +fn attachment_dtos_preserve_nullable_fields_and_storage_limits() { + use marreq_core::api::attachments::{AttachmentView, StorageView}; + let spec = spec(); + let timestamp = chrono::NaiveDate::from_ymd_opt(2026, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap(); + let attachment = AttachmentView { + id: 1, + entity_type: "requirement".into(), + entity_id: 2, + filename: "evidence.pdf".into(), + content_type: "application/pdf".into(), + size_bytes: 100, + uploaded_by: None, + uploaded_by_name: None, + created_at: timestamp, + deleted: false, + }; + let storage = StorageView { + used_bytes: 100, + quota_bytes: 1024, + quota_is_default: true, + default_quota_bytes: 1024, + retained_by_baselines_bytes: 0, + max_file_bytes: 512, + allowed_extensions: vec!["pdf"], + }; + for (name, value) in [ + ("AttachmentView", serde_json::to_value(attachment).unwrap()), + ("AttachmentStorage", serde_json::to_value(storage).unwrap()), + ] { + let schema = &spec["components"]["schemas"][name]; + marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + let mut broken = value; + let key = schema["required"][0].as_str().unwrap(); + broken.as_object_mut().unwrap().remove(key); + assert!(marreq_contracts::contract::validate_instance(&spec, schema, &broken).is_err()); + } +} diff --git a/mcp-server/dist/client.js b/mcp-server/dist/client.js index 35f319ba..8ff7f956 100644 --- a/mcp-server/dist/client.js +++ b/mcp-server/dist/client.js @@ -47,16 +47,12 @@ export class MarreqClient { async getRequirement(id) { return this.request(`/api/projects/${this.ctx.projectId}/requirements/${id}`); } - async listRequirements(approvalState, hasTests, extra = {}) { + async listRequirements(approvalState, hasTests) { const params = new URLSearchParams(); if (approvalState != null) params.set("approval_state", approvalState); if (hasTests != null) params.set("has_tests", String(hasTests)); - for (const [key, value] of Object.entries(extra)) { - if (value != null && value !== "") - params.set(key, String(value)); - } const q = params.toString(); return this.request(`/api/projects/${this.ctx.projectId}/requirements${q ? `?${q}` : ""}`); } @@ -277,104 +273,6 @@ export class MarreqClient { body: JSON.stringify(payload), }); } - // ----- Tool gaps (issue #416) ----- - project(path) { - return `/api/projects/${this.ctx.projectId}${path}`; - } - withQuery(path, query) { - const params = new URLSearchParams(); - for (const [key, value] of Object.entries(query)) { - if (value != null && value !== "") - params.set(key, String(value)); - } - const q = params.toString(); - return q ? `${path}?${q}` : path; - } - async listRequirementLinkTypes() { - return this.request(this.project("/requirement-version-links/link-types")); - } - async listRequirementLinks(query = {}) { - return this.request(this.withQuery(this.project("/requirement-version-links"), query)); - } - async createRequirementLink(payload, idempotencyKey) { - return this.request(this.project("/requirement-version-links"), { - method: "POST", - headers: { "Idempotency-Key": idempotencyKey }, - body: JSON.stringify(payload), - }); - } - async deleteRequirementLink(linkId) { - return this.request(this.project(`/requirement-version-links/${linkId}`), { method: "DELETE" }); - } - async deleteUseCaseLink(useCaseId, linkId) { - return this.request(this.useCases(`/${useCaseId}/links/${linkId}`), { method: "DELETE" }); - } - async clearUseCaseLinkSuspect(useCaseId, linkId) { - return this.request(this.useCases(`/${useCaseId}/links/${linkId}/clear_suspect`), { method: "POST" }); - } - async listLinkedUseCases(kind, id) { - return this.request(this.project(`/${kind}s/${id}/use-cases`)); - } - async askRequirements(payload) { - return this.request(this.project("/requirements/ask"), { method: "POST", body: JSON.stringify(payload) }); - } - async listReviewers() { - return this.request(this.project("/reviewers")); - } - async getMyPermissions() { - return this.request(this.project("/me/permissions")); - } - async listSavedViews() { - return this.request(this.project("/saved_views")); - } - async listAttachments(entityType, entityId) { - return this.request(this.withQuery(this.project("/attachments"), { entity_type: entityType, entity_id: entityId })); - } - /** What needs the user's attention across projects (not project-scoped). */ - async getAttention() { - return this.request("/api/home/attention"); - } - async listUseCaseVersions(useCaseId) { - return this.request(this.useCases(`/${useCaseId}/versions`)); - } - async listUseCaseComments(useCaseId, query = {}) { - return this.request(this.withQuery(this.useCases(`/${useCaseId}/comments`), query)); - } - async createUseCaseComment(useCaseId, payload, idempotencyKey) { - return this.request(this.useCases(`/${useCaseId}/comments`), { - method: "POST", - headers: { "Idempotency-Key": idempotencyKey }, - body: JSON.stringify(payload), - }); - } - async discardUseCaseDraft(useCaseId) { - return this.request(this.useCases(`/${useCaseId}/draft`), { method: "DELETE" }); - } - /** Project close-out, or one requirement's when requirementId is given. */ - async getCloseOut(requirementId) { - return this.request(this.project(requirementId != null ? `/requirements/${requirementId}/close_out` : "/close_out")); - } - async getImpactedTests(requirementId) { - return this.request(this.project(`/requirements/${requirementId}/impacted_tests`)); - } - async listVerificationSnapshots(verificationId) { - return this.request(this.project(`/verifications/${verificationId}/snapshots`)); - } - async compareVerificationSnapshots(verificationId, v1, v2) { - return this.request(this.project(`/verifications/${verificationId}/snapshots/${v1}/diff/${v2}`)); - } - async setVerificationField(verificationId, field, value) { - return this.request(this.project(`/verifications/${verificationId}/field`), { - method: "POST", - body: JSON.stringify({ field, value }), - }); - } - async getBaselineContents(baselineId, kind) { - return this.request(this.project(`/baselines/${baselineId}/${kind === "use_cases" ? "use-cases" : "verifications"}`)); - } - async diffBaselineVerificationVsCurrent(baselineId, verificationId) { - return this.request(this.project(`/baselines/${baselineId}/verifications/${verificationId}/diff/current`)); - } get projectId() { return this.ctx.projectId; } diff --git a/mcp-server/dist/index.js b/mcp-server/dist/index.js index 4e1efafb..f19cf540 100644 --- a/mcp-server/dist/index.js +++ b/mcp-server/dist/index.js @@ -7,10 +7,6 @@ import { z } from "zod"; import { contextAllowsReadExtended, loadContext } from "./context.js"; import { MarreqAuthenticationError, MarreqClient } from "./client.js"; import { loadTransportConfig, startRemoteServer } from "./remote.js"; -import { registerGapTools } from "./gap-tools.js"; -import { paginate, paginationFields } from "./pagination.js"; -import { registerResources } from "./resources.js"; -import { registerPrompts } from "./prompts.js"; /** Requirement statements and verification descriptions use "Marreq statement Markdown" (marreq-core/src/rich_text.rs). */ const DESCRIPTION_FORMAT = "Verification description in Marreq statement Markdown: paragraphs separated by a blank line, '- ' bulleted and '1. ' numbered lists, **bold**, *italic*, `code`, [label](https://...) links (http/https/mailto only). Max 2000 bytes including markup."; const STATEMENT_FORMAT = "Requirement statement in Marreq statement Markdown: paragraphs separated by a blank line, '- ' bulleted and '1. ' numbered lists, **bold**, *italic*, `code`, [label](https://...) links (http/https/mailto only). Max 2000 bytes including markup."; @@ -133,31 +129,6 @@ export function createMarreqServer(ctx) { edit_use_case_draft: ["use_cases:write"], publish_use_case_draft: ["use_cases:write"], link_use_case_element: ["use_cases:write"], - // Tool gaps (issue #416) - list_requirement_link_types: ["traceability:read"], - list_requirement_links: ["traceability:read"], - link_requirements: ["traceability:write"], - unlink_requirements: ["traceability:write"], - unlink_use_case_element: ["use_cases:write"], - clear_use_case_link_suspect: ["use_cases:write"], - list_linked_use_cases: ["use_cases:read"], - ask_requirements: ["requirements:read"], - list_reviewers: ["projects:read"], - get_my_permissions: ["projects:read"], - list_saved_views: ["requirements:read"], - list_attachments: ["requirements:read", "verifications:read"], - get_attention: ["projects:read"], - list_use_case_versions: ["use_cases:read"], - list_use_case_comments: ["use_cases:read"], - create_use_case_comment: ["use_cases:write"], - discard_use_case_draft: ["use_cases:write"], - get_close_out: ["requirements:read"], - impacted_tests: ["requirements:read", "verifications:read"], - list_verification_snapshots: ["verifications:read"], - compare_verification_snapshots: ["verifications:read"], - set_verification_field: ["verifications:write"], - get_baseline_contents: ["baselines:read"], - diff_baseline_verification_vs_current: ["verifications:read", "baselines:read"], }; const originalRegisterTool = server.registerTool.bind(server); const unsafeRegisterTool = originalRegisterTool; @@ -171,8 +142,6 @@ export function createMarreqServer(ctx) { "create_verification", "create_use_case", "publish_use_case_draft", - "link_requirements", - "create_use_case_comment", ].includes(name); const annotations = { readOnlyHint: !mutating, @@ -203,11 +172,11 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_projects", { description: "List only projects accessible to the authenticated Marreq user.", - inputSchema: z.object({ ...paginationFields }), + inputSchema: z.object({}), annotations: { readOnlyHint: true }, - }, async (args) => { + }, async () => { const out = await withAudit(client, "list_projects", "{}", false, () => client.listProjects()); - return { content: [jsonContent(paginate(out, args ?? {}))] }; + return { content: [jsonContent(out)] }; }); server.registerTool("get_requirement", { description: "Get a requirement by id (project-scoped, with trace summary)", @@ -219,35 +188,21 @@ export function createMarreqServer(ctx) { return { content: [jsonContent(out)] }; }); server.registerTool("list_requirements", { - description: "List requirements in the project with optional filters (approval_state, has_tests, status_id, category_id, free-text q, a saved view_id from list_saved_views) and sorting (sort_column, sort_dir). Pass limit (and cursor from next_cursor) to page; without them the whole list is returned.", + description: "List requirements in the project with optional filters (approval_state, has_tests)", inputSchema: z.object({ ...projectField, filter: z .object({ approval_state: z.enum(["draft", "reviewed", "approved"]).optional(), has_tests: z.boolean().optional(), - status_id: z.number().int().positive().optional(), - category_id: z.number().int().positive().optional(), - q: z.string().optional().describe("Text to find in code, title or statement"), - view_id: z.number().int().positive().optional().describe("Saved view id (list_saved_views)"), }) .optional(), - sort_column: z.string().optional(), - sort_dir: z.enum(["asc", "desc"]).optional(), - ...paginationFields, }), }, async (args) => { const f = args?.filter; const toolClient = forProject(args?.project_id); - const out = await withAudit(toolClient, "list_requirements", JSON.stringify(args ?? {}), false, () => toolClient.listRequirements(f?.approval_state, f?.has_tests, { - status_id: f?.status_id, - category_id: f?.category_id, - q: f?.q, - view_id: f?.view_id, - sort_column: args?.sort_column, - sort_dir: args?.sort_dir, - })); - return { content: [jsonContent(paginate(out, args ?? {}))] }; + const out = await withAudit(toolClient, "list_requirements", JSON.stringify(args ?? {}), false, () => toolClient.listRequirements(f?.approval_state, f?.has_tests)); + return { content: [jsonContent(out)] }; }); server.registerTool("get_versions", { description: "Get version history for a requirement", @@ -356,12 +311,11 @@ export function createMarreqServer(ctx) { q: z.string().optional().describe("Filter on code, title or actor"), approval_state: z.enum(["draft", "reviewed", "approved", "unpublished"]).optional(), has_suspect: z.boolean().optional().describe("Only use cases with suspect links"), - ...paginationFields, }), - }, async ({ project_id, q, approval_state, has_suspect, limit, cursor }) => { + }, async ({ project_id, q, approval_state, has_suspect }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_use_cases", JSON.stringify({ q, approval_state, has_suspect }), false, () => toolClient.listUseCases({ q, approval_state, has_suspect })); - return { content: [jsonContent(paginate(out, { limit, cursor }))] }; + return { content: [jsonContent(out)] }; }); server.registerTool("get_use_case", { description: "Get a use case: its current version (or version_id) as a marreq.use-case.v1 document with computed labels (1, 2, A3, A3.1, E4), and the shared draft (document, labels, revision) when one exists. Element ids such as s_โ€ฆ and b_โ€ฆ are stable; labels are not.", @@ -393,21 +347,21 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_use_case_links", { description: "Requirements and verifications linked to a use case or to one of its steps, with suspect flags (a linked requirement changed since the link was made or cleared).", - inputSchema: z.object({ ...projectField, use_case_id: z.number().int().positive(), ...paginationFields }), - }, async ({ project_id, use_case_id, limit, cursor }) => { + inputSchema: z.object({ ...projectField, use_case_id: z.number().int().positive() }), + }, async ({ project_id, use_case_id }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_use_case_links", JSON.stringify({ use_case_id }), false, () => toolClient.listUseCaseLinks(use_case_id)); - return { content: [jsonContent(paginate(out, { limit, cursor }))] }; + return { content: [jsonContent(out)] }; }); // read_extended / draft_write: extra read tools (catalog, verifications, audit, matrix read, โ€ฆ) if (contextAllowsReadExtended(ctx)) { server.registerTool("list_verifications", { description: "List verifications (tests) in the project. Requires MARREQ_MODE=read_extended or draft_write.", - inputSchema: z.object({ ...projectField, ...paginationFields }), - }, async ({ project_id, limit, cursor }) => { + inputSchema: z.object({ ...projectField }), + }, async ({ project_id }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_verifications", "{}", false, () => toolClient.listVerificationsByProject()); - return { content: [jsonContent(paginate(out, { limit, cursor }))] }; + return { content: [jsonContent(out)] }; }); server.registerTool("get_verification", { description: "Get one verification by id. The record must belong to MARREQ_PROJECT_ID. Requires read_extended or draft_write mode.", @@ -427,11 +381,11 @@ export function createMarreqServer(ctx) { }); server.registerTool("list_baselines", { description: "List baselines for the project (metadata only). Use get_baseline for full snapshot.", - inputSchema: z.object({ ...projectField, ...paginationFields }), - }, async ({ project_id, limit, cursor }) => { + inputSchema: z.object({ ...projectField }), + }, async ({ project_id }) => { const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_baselines", "{}", false, () => toolClient.listBaselinesByProject()); - return { content: [jsonContent(paginate(out, { limit, cursor }))] }; + return { content: [jsonContent(out)] }; }); server.registerTool("get_requirement_activity", { description: "Audit log entries for a requirement (create/update history with field summaries).", @@ -457,13 +411,12 @@ export function createMarreqServer(ctx) { ...projectField, requirement_id: z.string(), requirement_version_id: z.number().optional(), - ...paginationFields, }), - }, async ({ project_id, requirement_id, requirement_version_id, limit, cursor }) => { + }, async ({ project_id, requirement_id, requirement_version_id }) => { const id = parseInt(requirement_id, 10); const toolClient = forProject(project_id); const out = await withAudit(toolClient, "list_requirement_comments", JSON.stringify({ requirement_id, requirement_version_id }), false, () => toolClient.listRequirementComments(id, requirement_version_id ?? null)); - return { content: [jsonContent(paginate(out, { limit, cursor }))] }; + return { content: [jsonContent(out)] }; }); server.registerTool("get_verification_matrix", { description: "Requirement ids linked to a verification in the traceability matrix (read).", @@ -578,7 +531,6 @@ export function createMarreqServer(ctx) { reviewer_id: z.number().optional(), category_id: z.number().optional(), applicability_id: z.number().optional(), - justification: z.string().optional().describe("Why the requirement exists; an empty string clears it."), custom_fields: z .array(z.object({ field_id: z.number(), value: z.string() })) .optional(), @@ -756,18 +708,6 @@ export function createMarreqServer(ctx) { return { content: [jsonContent(out)] }; }); } - registerGapTools({ - server: server, - ctx, - client, - forProject, - projectField, - operationKey, - withAudit, - jsonContent, - }); - registerResources(server, { ctx, client, withAudit }); - registerPrompts(server); return server; } async function main() { diff --git a/mcp-server/src/generated-contract.ts b/mcp-server/src/generated-contract.ts index eb559813..cf8764e4 100644 --- a/mcp-server/src/generated-contract.ts +++ b/mcp-server/src/generated-contract.ts @@ -1,13 +1,6 @@ // Generated by scripts/generate-api-types.mjs; do not edit. export interface components { schemas: { - ApiError: { - error: string; - message: string; - status: number; - } & { - [key: string]: unknown; - }; McpPrincipalResponse: { authentication_type: string; client_id: string | null; @@ -17,15 +10,6 @@ export interface components { } & { [key: string]: unknown; }; - UseCaseBranch: { - end: components["schemas"]["UseCaseBranchEnd"]; - from_step: string; - id: string; - /** @enum {string} */ - kind: "alternative" | "exception"; - steps?: components["schemas"]["UseCaseElement"][]; - title: string; - }; UseCaseBranchEnd: { step: string; /** @constant */ @@ -45,67 +29,6 @@ export interface components { steps: components["schemas"]["UseCaseElement"][]; title: string; }; - UseCaseDependents: "cascade" | ({ - rebranch_to: string; - } & { - [key: string]: unknown; - }); - UseCaseDiff: { - elements: components["schemas"]["UseCaseElementDiff"][]; - from: components["schemas"]["UseCaseDiffEnd"] | null; - header: { - [key: string]: { - new: string | null | string[]; - old: string | null | string[]; - } & { - [key: string]: unknown; - }; - }; - summary: { - added: number; - edited: number; - moved: number; - rebranched: number; - removed: number; - } & { - [key: string]: unknown; - }; - to: components["schemas"]["UseCaseDiffEnd"]; - } & { - [key: string]: unknown; - }; - UseCaseDiffEnd: { - draft_revision: number | null; - version_id: number | null; - version_number: number | null; - } & { - [key: string]: unknown; - }; - UseCaseDiffSide: { - branch_id?: string; - end?: components["schemas"]["UseCaseBranchEnd"]; - from_step?: string; - /** @enum {string} */ - kind?: "alternative" | "exception"; - label: string | null; - /** @enum {string} */ - list: "preconditions" | "main" | "postconditions" | "branch" | "branches"; - text: string; - } & { - [key: string]: unknown; - }; - UseCaseDocument: { - branches?: components["schemas"]["UseCaseBranch"][]; - main?: components["schemas"]["UseCaseElement"][]; - postconditions?: components["schemas"]["UseCaseElement"][]; - preconditions?: components["schemas"]["UseCaseElement"][]; - primary_actor?: string | null; - /** @constant */ - schema?: "marreq.use-case.v1"; - secondary_actors?: string[]; - summary?: string | null; - title: string; - }; UseCaseDocumentResponse: { branches: components["schemas"]["UseCaseBranchResponse"][]; main: components["schemas"]["UseCaseElement"][]; @@ -150,113 +73,6 @@ export interface components { id: string; text: string; }; - UseCaseElementDiff: { - changes: ("added" | "removed" | "edited" | "moved" | "rebranched")[]; - id: string; - /** @enum {string} */ - kind: "precondition" | "postcondition" | "step" | "branch"; - new: components["schemas"]["UseCaseDiffSide"] | null; - old: components["schemas"]["UseCaseDiffSide"] | null; - text_diff?: ({ - /** @enum {string} */ - op: "equal" | "delete" | "insert"; - text: string; - } & { - [key: string]: unknown; - })[]; - } & { - [key: string]: unknown; - }; - UseCaseListRef: ("preconditions" | "main" | "postconditions") | ({ - branch: string; - } & { - [key: string]: unknown; - }); - UseCaseNewBranch: { - end: components["schemas"]["UseCaseBranchEnd"]; - from_step: string; - id?: string | null; - /** @enum {string} */ - kind: "alternative" | "exception"; - steps?: components["schemas"]["UseCaseNewElement"][]; - title: string; - }; - UseCaseNewElement: { - id?: string | null; - text: string; - }; - UseCaseOperation: { - /** @constant */ - op: "set_header"; - primary_actor?: string | null; - secondary_actors?: string[] | null; - summary?: string | null; - title?: string | null; - } | { - after?: string | null; - element: components["schemas"]["UseCaseNewElement"]; - list: components["schemas"]["UseCaseListRef"]; - /** @constant */ - op: "add_element"; - } | { - id: string; - /** @constant */ - op: "update_text"; - text: string; - } | { - after?: string | null; - id: string; - list?: components["schemas"]["UseCaseListRef"] | null; - /** @constant */ - op: "move_element"; - } | { - dependents?: components["schemas"]["UseCaseDependents"] | null; - id: string; - /** @constant */ - op: "remove_element"; - } | { - branch: components["schemas"]["UseCaseNewBranch"]; - /** @constant */ - op: "add_branch"; - } | { - end?: components["schemas"]["UseCaseBranchEnd"] | null; - from_step?: string | null; - id: string; - kind?: ("alternative" | "exception") | null; - /** @constant */ - op: "update_branch"; - title?: string | null; - } | { - id: string; - /** @constant */ - op: "remove_branch"; - }; - UseCasePublishResponse: { - removed_links: components["schemas"]["use_cases_LinkView_response"][]; - version: components["schemas"]["use_cases_VersionView_response"]; - } & { - [key: string]: unknown; - }; - use_cases_BaselineUseCaseView_response: { - approval_state: string; - current_version_id: number | null; - current_version_number: number | null; - link_count: number; - primary_actor: string | null; - reference_code: string; - title: string; - use_case_id: number; - version_id: number; - version_number: number; - } & { - [key: string]: unknown; - }; - use_cases_ClearSuspectResponse_response: { - cleared: boolean; - status: string; - } & { - [key: string]: unknown; - }; use_cases_DraftChangeView_response: { base_version_id: number | null; document: components["schemas"]["UseCaseDocumentResponse"]; @@ -295,51 +111,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_LinkView_response: { - created_at: string; - created_by: number | null; - current_requirement_version_id: number | null; - element_id: string | null; - element_label: string | null; - id: number; - linked_requirement_version_id: number | null; - rationale: string | null; - requirement_id: number | null; - suspect: boolean; - suspect_at: string | null; - suspect_reason: string | null; - target_reference_code: string; - target_title: string; - /** @enum {string} */ - target_type: "requirement" | "verification"; - verification_id: number | null; - } & { - [key: string]: unknown; - }; - use_cases_NextCode_response: { - reference_code: string; - } & { - [key: string]: unknown; - }; - use_cases_PutDraftRequest_request: { - base_revision?: number | null; - document: components["schemas"]["UseCaseDocument"]; - } & { - [key: string]: unknown; - }; - use_cases_UseCaseCommentView_response: { - author_id: number; - author_name: string; - body: string; - created_at: string; - element_id: string | null; - element_label: string | null; - id: number; - use_case_id: number; - use_case_version_id: number | null; - } & { - [key: string]: unknown; - }; use_cases_UseCaseDetail_response: { created_at: string; created_by: number | null; @@ -353,17 +124,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_UseCaseRef_response: { - element_id: string | null; - element_label: string | null; - link_id: number; - reference_code: string; - suspect: boolean; - title: string; - use_case_id: number; - } & { - [key: string]: unknown; - }; use_cases_UseCaseRow_response: { alternative_count: number; approval_state: string | null; @@ -381,23 +141,6 @@ export interface components { } & { [key: string]: unknown; }; - use_cases_VersionSummary_response: { - approval_state: string; - approved_at: string | null; - approved_by: number | null; - change_note: string | null; - created_at: string; - created_by: number | null; - created_by_name: string | null; - id: number; - primary_actor: string | null; - reviewed_at: string | null; - reviewed_by: number | null; - title: string; - version_number: number; - } & { - [key: string]: unknown; - }; use_cases_VersionView_response: { approval_state: string; approved_at: string | null; @@ -420,19 +163,4 @@ export interface components { [key: string]: unknown; }; }; - responses: { - /** @description API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. */ - ApiError: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ApiError"]; - }; - }; - }; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; } diff --git a/scripts/smoke-api-consumers.mjs b/scripts/smoke-api-consumers.mjs index 8de0086d..45692b41 100644 --- a/scripts/smoke-api-consumers.mjs +++ b/scripts/smoke-api-consumers.mjs @@ -4,7 +4,8 @@ import assert from 'node:assert/strict'; import { appendFileSync } from 'node:fs'; import { MarreqClient } from '../mcp-server/dist/client.js'; import { fetchJson } from '../frontend/src/api/transport.ts'; -import { getCsrfToken, loginJson, getMe, logoutJson } from '../frontend/src/api/auth.ts'; +import { getCsrfToken, loginJson, getMe, logoutJson, getConnectedIdentities, + getConnectedApplications, updateMyProfile, changePassword } from '../frontend/src/api/auth.ts'; import { requestPasswordReset } from '../frontend/src/api/publicAuth.ts'; const base = process.env.MARREQ_CONTRACT_BACKEND_URL; @@ -50,6 +51,14 @@ assert.deepEqual(discovery.token_endpoint_auth_methods_supported, ['none']); const csrf = await getCsrfToken(); await loginJson('manager', 'Contract-only-fixture-58!z', csrf); assert.equal((await getMe()).username, 'manager'); +assert.equal(typeof (await getConnectedIdentities()).password_configured, 'boolean'); +assert.ok(Array.isArray(await getConnectedApplications())); +const user = await getMe(); +const sessionCsrf = await getCsrfToken(); +assert.equal((await updateMyProfile({name:user.name, email:user.email}, sessionCsrf)).id, user.id); +await assert.rejects(() => changePassword({current_password:'wrong',new_password:'new',confirm_password:'different'}, sessionCsrf), (error) => error.status === 400); +// The real session is a project manager, not an instance administrator. +await assert.rejects(() => fetchJson('/api/cache/stats'), (error) => error.status === 403); await logoutJson(await getCsrfToken()); await assert.rejects(() => getMe(), (error) => error.status === 401); if (mode === 'cloud') await requestPasswordReset({email:'absent-contract-user@example.test'}); diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs index 097917ea..70203315 100644 --- a/tools/marreq-contracts/tests/compatibility.rs +++ b/tools/marreq-contracts/tests/compatibility.rs @@ -323,6 +323,25 @@ fn tampered_contract_hash() { .contains("SHA-256 mismatch") ); } + +#[test] +fn candidate_identity_requires_exact_source_version_and_bundle() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let bundle = dir.path().join("backend.json"); + let version = "0.2.4".parse().unwrap(); + let wrong_version = "0.2.5".parse().unwrap(); + let source = "a".repeat(40); + artifact::verify(&path, Some(("backend", &version, &source, &bundle))).unwrap(); + for (version, source) in [(&wrong_version, source.clone()), (&version, "b".repeat(40))] { + assert!(artifact::verify(&path, Some(("backend", version, &source, &bundle))).is_err()); + } + let altered = dir.path().join("altered.json"); + let mut document = contract::load(&bundle).unwrap(); + document["contract"]["paths"] = json!({}); + contract::write(&altered, &document).unwrap(); + assert!(artifact::verify(&path, Some(("backend", &version, &source, &altered))).is_err()); +} #[test] fn cli_exit_codes_and_json_diagnostics() { let dir = tempfile::tempdir().unwrap(); From 0e46958208305558bb1b7d83976159a8f12fe5a3 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Wed, 4 Nov 2026 19:35:57 +0100 Subject: [PATCH 06/18] fix(contracts): audit every frontend and MCP wire operation --- docs/developer/http-api-contract.md | 46 +- docs/developer/openapi.yaml | 11458 +++++++++++++++++--------- docs/releases.md | 5 +- 3 files changed, 7480 insertions(+), 4029 deletions(-) diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index be84b37e..cbe51353 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -61,7 +61,7 @@ and `/oauth/token`. See [authentication.md](authentication.md#delegated-applicat | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/dashboard` | Authenticated dashboard payload: `user`, decorated `projects` (same shape as the legacy HTML index), `projects_count`, `selected_project_id`, `selected_project_slug`, `csrf_token`. **401** if not logged in. | -| `GET` | `/api/home/attention` | What needs the user's attention across the projects listed for them (the start screen, issue #387), newest first, at most 50 `items`. Each item has `kind`, `project_id`, `title`, `at`, and, depending on the kind, `requirement_id`, `reference_code`, `count`, `notification_id`, `notification_type`. Kinds: **`approval`** (one per requirement whose current version is *reviewed*, in projects where the user may change review gates); **`review`** (one per such project, `count` = draft requirements); **`suspect`** (one per project where the user has `EditRequirements`, `count` = suspect matrix links); **`notification`** (unread `review_assigned`, `comment_added` and `mentioned` notifications; `mentioned` is created when a new requirement comment mentions a project member as `@username`, issue #389). Archived projects are skipped, except for notifications. Also returns the totals `approvals`, `reviews`, `suspect_links`, `notifications`. Session only (no Bearer tokens). **401** if not logged in. | +| `GET` | `/api/home/attention` | What needs the user's attention across the projects listed for them (the start screen, issue #387), newest first, at most 50 `items`. Each item has `kind`, `project_id`, `title`, `at`, and, depending on the kind, `requirement_id`, `reference_code`, `count`, `notification_id`, `notification_type`. Kinds: **`approval`** (one per requirement whose current version is *reviewed*, in projects where the user may change review gates); **`review`** (one per such project, `count` = draft requirements); **`suspect`** (one per project where the user has `EditRequirements`, `count` = suspect matrix links); **`notification`** (unread `review_assigned`, `comment_added` and `mentioned` notifications; `mentioned` is created when a new requirement comment mentions a project member as `@username`, issue #389). Archived projects are skipped, except for notifications. Also returns the totals `approvals`, `reviews`, `suspect_links`, `notifications`. Session or Bearer; delegated OAuth requires `projects:read`, and a token project scope narrows the result. **401** if not logged in. | | `GET` | `/api/search?q=&limit=` | Requirements whose reference code or title contains `q` (case-insensitive, at least 2 characters; shorter returns `[]`), across the projects the user can view. Ordered exact reference code, code prefix, code substring, then title match; `limit` defaults to 20, at most 50. Returns `[{project_id, requirement_id, reference_code, title}]`. Session only (no Bearer tokens). **401** if not logged in. | Successful login response (200): `{ "status": "ok", "user": { ... } }` (serialized `User` model). @@ -236,15 +236,12 @@ audit and cannot be used in a releasable consumer bundle. Unconsumed backend endpoints are outside this contract; adding/changing them does not itself block an existing consumer. -**PR #424 remains draft.** Discovery currently selects 195 frontend operations and -79 MCP operations: 217 distinct dependencies, of which 59 have audited wire -contracts and 158 remain unverified. The audited additions cover cache -administration, session/profile/identity flows, connected grants, multipart -attachments, file downloads and storage quotas, alongside the existing metadata, -OAuth, Cloud account and use-case contracts. These counts describe the current -source, not a separate inventory to maintain. Complete the remaining consumed -wire audits and producer regression protection before treating this as merge-ready. -A passing document validator alone is not a compatibility guarantee. +The current source selects **195 frontend operations and 79 MCP operations: 217 +distinct HTTP dependencies, all wire-audited** against the Rust routes, serializers +and guards. This includes JSON, multipart uploads, downloads, browser navigation, +sessions, OAuth and Server/Cloud differences. The unconsumed index-status inventory +entry remains outside mandatory consumer compatibility. Newly consumed unverified +or undocumented operations still fail closed. ### Architecture and rationale @@ -261,13 +258,15 @@ A passing document validator alone is not a compatibility guarantee. grammar. The analyzer follows finite URL helpers, local constants, templates, branch alternatives and explicit HTTP methods. It includes awaited generic calls, direct fetches, shared transports, multipart/download wrappers, and API - browser navigation. Unknown paths/methods fail; there is no source regex parser, + browser navigation, including location calls and assignments. Unsupported raw + network transports require an adapter; unknown paths/methods fail; there is no source regex parser, source-review checksum or separately maintained full route list. - The small protocol dependency list covers browser/OAuth callbacks and discovery which are not ordinary `fetch` expressions. Its IDs must exist in OpenAPI. All other dependencies are discovered from consumer source. The only dynamic href - exclusion is `StatementText`'s sanitized user-content link: user-authored - navigation is not an application HTTP client operation. It does not exclude + link exclusion is `StatementText`'s sanitized user-content link. Login return + navigation is user-selected, and identity linking follows the URL from its + audited API response; both existing flows have explicit source associations. It does not exclude fetches or fixed API links in that component. - Compiled Rocket mount tests check every declared consumer operation against Server/Cloud registration. Extra backend routes are permitted. Four public @@ -308,8 +307,8 @@ scripts/test-api-contracts.sh `validate` checks document/policy validity and requires a verified schema for **every discovered consumer dependency**. `--document-only` checks the document without certifying consumer coverage; it cannot create a consumer release bundle. -`select --allow-unverified` is used only for development type generation, never -for bundle export or compatibility verification. +`select` and type generation require verified consumer contracts too; there is +no development bypass for an unverified consumer operation. When adding/changing an HTTP call, update the actual Rocket implementation and OpenAPI, then regenerate declarations and extend existing consumer/producer tests. @@ -324,14 +323,21 @@ client. Test processes use a clean directory/environment, loopback addresses and synthetic credentials; existing `.env`/database/SMTP configuration is excluded. Transcripts omit headers/cookies and redact login credentials. Default workspace unit tests can unify `test-helpers`; the separate production-path run is essential. -It remains representative evidence, not certification of all unmodeled APIs. +It validates 87 exchanges with no unverified exchanges, requiring critical +evidence from **both** deployment variants. The existing Rocket integration tests +also assert actual response status, media, required headers and JSON bodies against +OpenAPI. Request mutation tests compare schema acceptance with the real handler +deserializers; report defaults and catalogs use their actual Rust serializers. ### Scope limits -The CLI reports exact operation and call-site counts. Keep the PR draft while any -required operation is unverified, a producer behavior lacks adequate contract -protection, or a required check fails. Hand-maintained schemas still need broader -Rust DTO/serialization verification; compiled route presence alone is insufficient. +The CLI reports operation and call-site counts; no generated inventory is kept. +Schemas remain reviewed declarations: update them alongside Rust changes and run +the handler/DTO contract checks. Extra backend routes are not completeness blockers. +Exact consumer bundle export also requires committed build configuration, including +Vite settings, rather than just committed source files. Release plans require real +historical evidence as described in the release guide; missing evidence is never +replaced by current main. The advisory policy is inherited unchanged from `main`, including its `crypto-bigint` exception tracked in [#425](https://github.com/MarreqSW/Marreq/issues/425). The newly reported `calamine` advisory RUSTSEC-2026-0436 is fixed with the targeted diff --git a/docs/developer/openapi.yaml b/docs/developer/openapi.yaml index 9aa3d5e6..9db89146 100644 --- a/docs/developer/openapi.yaml +++ b/docs/developer/openapi.yaml @@ -1,303 +1,331 @@ openapi: 3.1.0 -info: {title: Marreq HTTP API, version: 0.1.0, description: Consumer-facing HTTP contracts. Unconsumed backend routes - are outside compatibility enforcement. Required operations marked x-marreq-unverified fail verification; no - missing schemas are inferred from inventory entries.} -servers: -- {url: /} +info: + { + title: Marreq HTTP API, + version: 0.1.0, + description: Consumer-facing HTTP contracts. Unconsumed backend routes are outside compatibility enforcement. + Required operations marked x-marreq-unverified fail verification; no missing schemas are inferred from + inventory entries. + } +servers: [ { url: / } ] paths: /api/cache/stats: get: operationId: cache_stats description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator only; identical in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CacheStats" - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: Not an administrator, or CSRF check failed; body may be framework-generated. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/CacheStats" } } } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } + "500": { $ref: "#/components/responses/ApiError" } /api/cache/recommendations: get: operationId: cache_recommendations description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator only; identical in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CacheRecommendations" - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: Not an administrator, or CSRF check failed; body may be framework-generated. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/CacheRecommendations" } } } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } + "500": { $ref: "#/components/responses/ApiError" } /api/cache/clear: post: operationId: cache_clear description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator only; identical in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CacheActionResult" - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: Not an administrator, or CSRF check failed; body may be framework-generated. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/CacheActionResult" } } } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } + "500": { $ref: "#/components/responses/ApiError" } x-marreq-csrf: session-csrf /api/cache/cleanup: post: operationId: cache_cleanup description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator only; identical in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CacheCleanupResult" - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: Not an administrator, or CSRF check failed; body may be framework-generated. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/CacheCleanupResult" } } } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } + "500": { $ref: "#/components/responses/ApiError" } x-marreq-csrf: session-csrf /api/cache/reset-counters: post: operationId: cache_reset_counters description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator only; identical in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/CacheActionResult" - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: Not an administrator, or CSRF check failed; body may be framework-generated. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/CacheActionResult" } } } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } + "500": { $ref: "#/components/responses/ApiError" } x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{id}/activity: get: operationId: activity_requirement_activity_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/EntityActivityItem" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, + permission and project-boundary rules in Server and Cloud. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/verifications/{id}/activity: get: operationId: activity_verification_activity_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/EntityActivityItem" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, + permission and project-boundary rules in Server and Cloud. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/applicability: get: operationId: applicability_list parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Applicability" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ViewRequirements post: operationId: applicability_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "201": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/NewApplicability" } } } + } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/applicability/{id}: put: operationId: applicability_update - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string }, message: { type: string } } + required: [ status, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/NewApplicability" } } } + } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf delete: operationId: applicability_delete - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/attachments: get: operationId: attachments_list parameters: - - in: path - name: project_id - required: true - schema: - type: integer + - { in: path, name: project_id, required: true, schema: { type: integer } } - in: query name: entity_type required: true - schema: - type: string - enum: - - requirement - - verification - - in: query - name: entity_id - required: true - schema: - type: integer + schema: { type: string, enum: [ requirement, verification ] } + - { in: query, name: entity_id, required: true, schema: { type: integer } } responses: "200": description: Success - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/AttachmentView" - "400": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/AttachmentView" } } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } "422": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "500": - $ref: "#/components/responses/ApiError" + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;ViewRequirements;delegated-requirements-and-verifications-read - x-marreq-bearer-scopes: - - requirements:read - - verifications:read + x-marreq-bearer-scopes: [ requirements:read, verifications:read ] post: operationId: attachments_upload - parameters: - - in: path - name: project_id - required: true - schema: - type: integer + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: "201": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/AttachmentView" - "400": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentView" } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } "413": - description: Upload exceeds the file or project quota, or Rocket's request limit. The handler returns - ApiError JSON; Rocket can reject an oversized form before the handler with a framework-generated body. - "415": - $ref: "#/components/responses/ApiError" + description: Upload exceeds the file or project quota, or Rocket's request limit. The handler returns ApiError + JSON; Rocket can reject an oversized form before the handler with a framework-generated body. + "415": { $ref: "#/components/responses/ApiError" } "422": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "500": - $ref: "#/components/responses/ApiError" + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied x-marreq-csrf: session-csrf requestBody: @@ -307,159 +335,92 @@ paths: schema: type: object properties: - file: - type: string - format: binary - entity_type: - type: string - enum: - - requirement - - verification - entity_id: - type: integer - required: - - file - - entity_type - - entity_id + file: { type: string, format: binary } + entity_type: { type: string, enum: [ requirement, verification ] } + entity_id: { type: integer } + required: [ file, entity_type, entity_id ] additionalProperties: true /api/projects/{project_id}/attachments/{attachment_id}/download: get: operationId: attachments_download parameters: - - in: path - name: project_id - required: true - schema: - type: integer - - in: path - name: attachment_id - required: true - schema: - type: integer + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: attachment_id, required: true, schema: { type: integer } } responses: "200": description: Stored file served as a download using its detected media type. headers: - Content-Disposition: - required: true - schema: - type: string - X-Content-Type-Options: - required: true - schema: - type: string - const: nosniff - Content-Security-Policy: - required: true - schema: - type: string - const: default-src 'none'; sandbox - Cache-Control: - required: true - schema: - type: string - const: private, no-cache - content: - "*/*": - schema: - type: string - format: binary - "400": - $ref: "#/components/responses/ApiError" + Content-Disposition: { required: true, schema: { type: string } } + X-Content-Type-Options: { required: true, schema: { type: string, const: nosniff } } + Content-Security-Policy: { required: true, schema: { type: string, const: default-src 'none'; sandbox } } + Cache-Control: { required: true, schema: { type: string, const: "private, no-cache" } } + content: { "*/*": { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied /api/projects/{project_id}/storage: get: operationId: attachments_get_storage - parameters: - - in: path - name: project_id - required: true - schema: - type: integer + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/AttachmentStorage" - "400": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentStorage" } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied /api/projects/{project_id}/storage/quota: put: operationId: attachments_set_quota - parameters: - - in: path - name: project_id - required: true - schema: - type: integer + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/AttachmentStorage" - "400": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentStorage" } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } "422": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "500": - $ref: "#/components/responses/ApiError" + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-authorization: instance-admin;not-archived x-marreq-csrf: session-csrf requestBody: @@ -468,127 +429,65 @@ paths: application/json: schema: type: object - properties: - quota_mb: - anyOf: - - type: integer - minimum: 1 - maximum: 1073741824 - - type: "null" + properties: { quota_mb: { anyOf: [ { type: integer, minimum: 1, maximum: 1073741824 }, { type: "null" } ] } } additionalProperties: false /api/projects/{project_id}/baselines/{baseline_id}/attachments: get: operationId: attachments_list_for_baseline parameters: - - in: path - name: project_id - required: true - schema: - type: integer - - in: path - name: baseline_id - required: true - schema: - type: integer + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: "200": description: Success - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/AttachmentView" - "400": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/AttachmentView" } } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read - x-marreq-bearer-scopes: - - baselines:read + x-marreq-bearer-scopes: [ baselines:read ] /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: get: operationId: attachments_download_for_baseline parameters: - - in: path - name: project_id - required: true - schema: - type: integer - - in: path - name: baseline_id - required: true - schema: - type: integer - - in: path - name: attachment_id - required: true - schema: - type: integer + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } + - { in: path, name: attachment_id, required: true, schema: { type: integer } } responses: "200": description: Stored file served as a download using its detected media type. headers: - Content-Disposition: - required: true - schema: - type: string - X-Content-Type-Options: - required: true - schema: - type: string - const: nosniff - Content-Security-Policy: - required: true - schema: - type: string - const: default-src 'none'; sandbox - Cache-Control: - required: true - schema: - type: string - const: private, no-cache - content: - "*/*": - schema: - type: string - format: binary - "400": - $ref: "#/components/responses/ApiError" + Content-Disposition: { required: true, schema: { type: string } } + X-Content-Type-Options: { required: true, schema: { type: string, const: nosniff } } + Content-Security-Policy: { required: true, schema: { type: string, const: default-src 'none'; sandbox } } + Cache-Control: { required: true, schema: { type: string, const: "private, no-cache" } } + content: { "*/*": { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read - x-marreq-bearer-scopes: - - baselines:read + x-marreq-bearer-scopes: [ baselines:read ] /api/auth/providers: get: operationId: auth_auth_providers @@ -597,36 +496,20 @@ paths: responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/external_ProviderDiscovery_response'} + content: { application/json: { schema: { $ref: '#/components/schemas/external_ProviderDiscovery_response' } } } /api/auth/external/{provider}/start: get: operationId: auth_auth_external_start parameters: - - in: path - name: provider - required: true - schema: - type: string - - in: query - name: return_to - schema: - type: string + - { in: path, name: provider, required: true, schema: { type: string } } + - { in: query, name: return_to, schema: { type: string } } responses: "303": description: Redirect to the provider or validated local return path. - headers: - Location: - required: true - schema: - type: string - "400": - $ref: "#/components/responses/ApiError" - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + headers: { Location: { required: true, schema: { type: string } } } + "400": { $ref: "#/components/responses/ApiError" } + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. security: [] @@ -634,41 +517,19 @@ paths: get: operationId: auth_auth_external_callback parameters: - - in: path - name: provider - required: true - schema: - type: string - - in: query - name: code - schema: - type: string - - in: query - name: state - schema: - type: string - - in: query - name: error - schema: - type: string + - { in: path, name: provider, required: true, schema: { type: string } } + - { in: query, name: code, schema: { type: string } } + - { in: query, name: state, schema: { type: string } } + - { in: query, name: error, schema: { type: string } } responses: "303": description: Redirect to the provider or validated local return path. - headers: - Location: - required: true - schema: - type: string - "400": - $ref: "#/components/responses/ApiError" - "401": - $ref: "#/components/responses/ApiError" - "404": - $ref: "#/components/responses/ApiError" - "409": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + headers: { Location: { required: true, schema: { type: string } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": { $ref: "#/components/responses/ApiError" } + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. security: [] @@ -680,1050 +541,2250 @@ paths: responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/AuthIdentities" - "401": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/AuthIdentities" } } } + "401": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] /api/auth/me: get: operationId: auth_auth_me - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] parameters: [] responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/entities_User_response'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} + content: { application/json: { schema: { $ref: '#/components/schemas/entities_User_response' } } } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } '500': description: API error - content: - application/json: - schema: {$ref: '#/components/schemas/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/ApiError' } } } put: operationId: auth_auth_update_me parameters: [] responses: "200": description: Success - content: - application/json: - schema: - $ref: "#/components/schemas/entities_User_response" - "400": - $ref: "#/components/responses/ApiError" - "401": - $ref: "#/components/responses/ApiError" - "403": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "404": - $ref: "#/components/responses/ApiError" - "409": - $ref: "#/components/responses/ApiError" - "422": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "500": - $ref: "#/components/responses/ApiError" + content: { application/json: { schema: { $ref: "#/components/schemas/entities_User_response" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": { $ref: "#/components/responses/ApiError" } + "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and services/user_service.rs. Own profile only. Email changes require the current password when configured; Cloud rejects email changes. Other profile changes work in both variants. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AuthProfileUpdate" + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/AuthProfileUpdate" } } } + } x-marreq-csrf: session-csrf /api/admin/backup/info: get: operationId: backup_info parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/BackupInfo" } } } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "410": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud + returns 410 because allows_database_backup is false. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin;allows_database_backup /api/admin/backup: post: operationId: backup_create - parameters: [] + parameters: [ { in: query, name: attachments, schema: { type: boolean }, required: false } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Server backup archive; Cloud returns 410. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/gzip: { schema: { type: string, format: binary } } } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "410": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud + returns 410 because allows_database_backup is false. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin;allows_database_backup + x-marreq-csrf: session-csrf /api/projects/{project_id}/baselines: get: operationId: baselines_list - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Baseline" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements post: operationId: baselines_create parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + [ + { in: path, name: project_id, required: true, schema: { type: integer } }, + { + in: header, + name: Idempotency-Key, + required: false, + schema: { type: string }, + description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore + or hyphen. Reuse with a different payload returns 409." + } + ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + { + description: Success, + content: { application/json: { schema: { $ref: "#/components/schemas/Baseline" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + saved_view_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ name ] + additionalProperties: true + x-marreq-bearer-scopes: [ baselines:write ] + x-marreq-authorization: project-membership;EditRequirements;visible-saved-view;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/baselines/{baseline_id}: get: operationId: baselines_get parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + { + description: Success, + content: { application/json: { schema: { $ref: "#/components/schemas/Baseline" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/{baseline_id}/requirements: get: operationId: baselines_get_requirements parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Requirement" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/{baseline_id}/traceability: get: operationId: baselines_get_traceability parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + { + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/BaselineTraceability" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/{baseline_id}/verifications: get: operationId: baselines_get_verifications parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + { + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/BaselineVerification" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/diff: get: operationId: baselines_diff_baselines parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: baseline_a, schema: { type: integer }, required: true } + - { in: query, name: baseline_b, schema: { type: integer }, required: true } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/BaselineDiff" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable + source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to + create. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/catalog: get: operationId: catalog_get - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + categories: { type: array, items: { $ref: "#/components/schemas/Category" } } + applicability: { type: array, items: { $ref: "#/components/schemas/Applicability" } } + requirement_statuses: { type: array, items: { $ref: "#/components/schemas/RequirementStatus" } } + verification_statuses: { type: array, items: { $ref: "#/components/schemas/VerificationStatus" } } + verification_methods: { type: array, items: { $ref: "#/components/schemas/VerificationMethod" } } + custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldDefinition" } } + required: + - categories + - applicability + - requirement_statuses + - verification_statuses + - verification_methods + - custom_fields + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/catalog.rs repository serializers. Project-scoped lookup arrays in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/categories: get: operationId: categories_list parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Category" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ViewRequirements post: operationId: categories_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/NewCategory" } } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/categories/{id}: put: operationId: categories_update - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string }, message: { type: string } } + required: [ status, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/NewCategory" } } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf delete: operationId: categories_delete - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{requirement_id}/comments: get: operationId: comments_list_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: requirement_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: requirement_id, required: true, schema: { type: integer } } + - { in: query, name: version_id, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CommentResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements post: operationId: comments_create_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: requirement_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: requirement_id, required: true, schema: { type: integer } } + - in: header + name: Idempotency-Key + required: false + schema: { type: string } + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore + or hyphen. Reuse with a different payload returns 409. responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "201": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/CommentResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { body: { type: string }, requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } } + required: [ body ] + additionalProperties: true + x-marreq-bearer-scopes: [ requirements:write ] + x-marreq-authorization: project-membership;EditRequirements;not-archived;approved-version-comment-lock-policy + x-marreq-csrf: session-csrf /api/requirements/{requirement_id}/comments: get: operationId: comments_list parameters: - - in: path - name: requirement_id - required: true - schema: {type: integer} + - { in: path, name: requirement_id, required: true, schema: { type: integer } } + - { in: query, name: version_id, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CommentResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;ViewRequirements post: operationId: comments_create - parameters: - - in: path - name: requirement_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: requirement_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "201": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/CommentResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { body: { type: string }, requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } } + required: [ body ] + additionalProperties: true + x-marreq-authorization: session;EditRequirements;not-archived;approved-version-comment-lock-policy + x-marreq-csrf: session-csrf /api/projects/{project_id}/custom_fields: get: operationId: custom_fields_list_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CustomFieldDefinition" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field + validation apply in both variants; enum_values is arbitrary stored JSON or null. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements post: operationId: custom_fields_create - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field + validation apply in both variants; enum_values is arbitrary stored JSON or null. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/CustomFieldDefinitionPayload" } } } + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/custom_fields/{field_id}: put: operationId: custom_fields_update parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: field_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: field_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string }, message: { type: string } } + required: [ status, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field + validation apply in both variants; enum_values is arbitrary stored JSON or null. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/CustomFieldDefinitionPayload" } } } + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf delete: operationId: custom_fields_delete parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: field_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/dsm: - get: - operationId: dsm_get_dsm - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: field_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/exports/requirements.xlsx: - get: - operationId: exports_export_requirements_xlsx - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string }, message: { type: string } } + required: [ status, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field + validation apply in both variants; enum_values is arbitrary stored JSON or null. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf + /api/projects/{project_id}/dsm: + get: + operationId: dsm_get_dsm + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: link_types, schema: { type: string }, required: false } + - { in: query, name: category_id, schema: { type: integer }, required: false } + - { in: query, name: root_id, schema: { type: integer }, required: false } + - { in: query, name: order, schema: { type: string, enum: [ hierarchy, partition ] }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Dsm" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/dsm.rs and dsm_service.rs output structs. Comma-separated link types, optional + project category/root, hierarchy or partition order; numeric indices and loop paths are preserved. + Shared in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read, traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements + /api/projects/{project_id}/exports/requirements.xlsx: + get: + operationId: exports_export_requirements_xlsx + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] + responses: + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/verifications.xlsx: get: operationId: exports_export_verifications_xlsx - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/matrix.xlsx: get: operationId: exports_export_matrix_xlsx - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/dsm.xlsx: get: operationId: exports_export_dsm_xlsx parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: link_types, schema: { type: string }, required: false } + - { in: query, name: category_id, schema: { type: integer }, required: false } + - { in: query, name: root_id, schema: { type: integer }, required: false } + - { in: query, name: order, schema: { type: string, enum: [ hierarchy, partition ] }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/matrix-links.xlsx: get: operationId: exports_export_matrix_links_xlsx - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/requirements.pdf: get: operationId: exports_export_requirements_pdf - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/pdf: { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/report.pdf: get: operationId: exports_export_report_pdf - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/pdf: { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/requirements.reqif: get: operationId: exports_export_requirements_reqif - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/xml: { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/requirements.reqifz: get: operationId: exports_export_requirements_reqifz - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/zip: { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/baselines/{filename}: get: operationId: exports_export_baseline_reqif parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: filename - required: true - schema: {type: string} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: filename, required: true, schema: { type: string } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/xml: { schema: { type: string, format: binary } } + application/zip: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/bundle.json: get: operationId: exports_export_project_bundle - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/json: { schema: { type: object } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/exports/bundle.zip: get: operationId: exports_export_project_bundle_zip - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: { application/zip: { schema: { type: string, format: binary } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the + same media and mandatory download filename header. Consumers treat file contents as opaque downloads; + the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated + server-side. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/groups: get: operationId: groups_list parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup post: operationId: groups_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ name ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;authenticated + x-marreq-csrf: session-csrf /api/groups/creatable: get: operationId: groups_list_creatable parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjects /api/groups/{group_id}: get: operationId: groups_get - parameters: - - in: path - name: group_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup patch: operationId: groups_update - parameters: - - in: path - name: group_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { name: { type: string }, description: { anyOf: [ { type: string }, { type: "null" } ] } } + required: [ name ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf delete: operationId: groups_delete - parameters: - - in: path - name: group_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf /api/groups/{group_id}/projects: get: operationId: groups_list_projects - parameters: - - in: path - name: group_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/groups/{group_id}/members: - get: - operationId: groups_list_members - parameters: - - in: path - name: group_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Project" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup + /api/groups/{group_id}/members: + get: + operationId: groups_list_members + parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] + responses: + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupMemberResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup /api/groups/{group_id}/members/{user_id}: put: operationId: groups_set_member_role parameters: - - in: path - name: group_id - required: true - schema: {type: integer} - - in: path - name: user_id - required: true - schema: {type: integer} + - { in: path, name: group_id, required: true, schema: { type: integer } } + - { in: path, name: user_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/GroupMemberResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: { type: object, properties: { role: { type: integer } }, required: [ role ], additionalProperties: true } + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf delete: operationId: groups_remove_member parameters: - - in: path - name: group_id - required: true - schema: {type: integer} - - in: path - name: user_id - required: true - schema: {type: integer} + - { in: path, name: group_id, required: true, schema: { type: integer } } + - { in: path, name: user_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session + or personal API token, delegated OAuth denied. Group membership and the operation-specific group + permission are enforced. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf /api/home/attention: get: operationId: home_attention parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + items: { type: array, items: { $ref: "#/components/schemas/AttentionItem" } } + approvals: { type: integer } + reviews: { type: integer } + suspect_links: { type: integer } + notifications: { type: integer } + required: [ items, approvals, reviews, suspect_links, notifications ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/home.rs AttentionItem/AttentionResponse and ProjectsRead guard. Both variants; at + most 50 attention items, nullable entity fields are serialized explicitly. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ projects:read ] + x-marreq-authorization: current-project-membership;token-project-scope /api/search: get: operationId: home_search - parameters: [] + parameters: + - { in: query, name: q, schema: { type: string }, required: true } + - { in: query, name: limit, schema: { type: integer, minimum: 0 }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: array + items: + type: object + properties: + kind: { type: string, enum: [ requirement, use_case ] } + project_id: { type: integer } + requirement_id: { anyOf: [ { type: integer }, { type: "null" } ] } + use_case_id: { anyOf: [ { type: integer }, { type: "null" } ] } + reference_code: { type: string } + title: { type: string } + required: [ kind, project_id, requirement_id, use_case_id, reference_code, title ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/home.rs SearchHit and search. Session-only in both variants; limit is clamped, short + queries yield an empty array. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;ViewRequirements /api/projects/{project_id}/imports/excel/preview: post: operationId: imports_preview_excel - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + import_type: { type: string } + columns: + type: array + items: + type: object + properties: { index: { type: integer }, name: { type: string }, sample_value: { type: string } } + required: [ index, name, sample_value ] + additionalProperties: true + sample_rows: { type: array, items: { type: array, items: { type: string } } } + row_count: { type: integer } + available_fields: + type: object + properties: + requirements: { type: array, items: { type: string } } + tests: { type: array, items: { type: string } } + matrix: { type: array, items: { type: string } } + required: [ requirements, tests, matrix ] + additionalProperties: true + unique_values: { type: object, additionalProperties: { type: array, items: { type: string } } } + required: [ import_type, columns, sample_rows, row_count, available_fields, unique_values ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "413": { $ref: "#/components/responses/ApiError" } + "415": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project + permissions. Size/content validation and Rocket multipart failures can return framework bodies. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: { file: { type: string, format: binary } } + required: [ file ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/imports/excel: post: operationId: imports_commit_excel - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/ExcelImportResult" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "413": { $ref: "#/components/responses/ApiError" } + "415": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project + permissions. Size/content validation and Rocket multipart failures can return framework bodies. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: { type: string, format: binary } + import_type: { type: string } + column_mappings: { type: string } + value_mappings: { type: string } + required: [ file, import_type, column_mappings ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/imports/reqif: post: operationId: imports_commit_reqif - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + message: { type: string } + imported_count: { type: integer } + created_link_count: { type: integer } + imported_attachment_count: { type: integer } + documents: { type: array, items: { type: string } } + errors: { type: array, items: { type: string } } + warnings: { type: array, items: { type: string } } + imported_requirement_ids: { type: array, items: { type: integer } } + required: + - success + - message + - imported_count + - created_link_count + - imported_attachment_count + - documents + - errors + - warnings + - imported_requirement_ids + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "413": { $ref: "#/components/responses/ApiError" } + "415": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project + permissions. Size/content validation and Rocket multipart failures can return framework bodies. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: { file: { type: string, format: binary } } + required: [ file ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/imports/bundle: post: operationId: imports_import_project_bundle parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + project_id: { type: integer } + slug: { type: string } + project_base_path: { type: string } + imported_counts: + type: object + properties: + categories: { type: integer } + applicability: { type: integer } + requirement_statuses: { type: integer } + verification_statuses: { type: integer } + verification_methods: { type: integer } + custom_fields: { type: integer } + requirements: { type: integer } + verifications: { type: integer } + matrix_links: { type: integer } + comments: { type: integer } + members: { type: integer } + reviewers: { type: integer } + attachments: { type: integer } + use_cases: { type: integer } + use_case_links: { type: integer } + required: + - categories + - applicability + - requirement_statuses + - verification_statuses + - verification_methods + - custom_fields + - requirements + - verifications + - matrix_links + - comments + - members + - reviewers + - attachments + - use_cases + - use_case_links + additionalProperties: true + warnings: { type: array, items: { type: string } } + errors: { type: array, items: { type: string } } + required: [ project_id, slug, project_base_path, imported_counts, warnings, errors ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "413": { $ref: "#/components/responses/ApiError" } + "415": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project + permissions. Size/content validation and Rocket multipart failures can return framework bodies. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: { file: { type: string, format: binary }, group_id: { type: integer } } + required: [ file ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects + x-marreq-csrf: session-csrf /api/admin/logs: get: operationId: logs_list - parameters: [] + parameters: + - { in: query, name: entity_type, schema: { type: string }, required: false } + - { in: query, name: action_type, schema: { type: string }, required: false } + - { in: query, name: since, schema: { type: string }, required: false } + - { in: query, name: until, schema: { type: string }, required: false } + - { in: query, name: entity_id, schema: { type: integer }, required: false } + - { in: query, name: user_id, schema: { type: integer }, required: false } + - { in: query, name: project_id, schema: { type: integer }, required: false } + - { in: query, name: limit, schema: { type: integer }, required: false } + - { in: query, name: offset, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AdminLogList" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both + variants. Datetime filters are parsed server-side; pagination and top are clamped. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin /api/admin/logs/stats: get: operationId: logs_stats - parameters: [] + parameters: + - { in: query, name: entity_type, schema: { type: string }, required: false } + - { in: query, name: action_type, schema: { type: string }, required: false } + - { in: query, name: since, schema: { type: string }, required: false } + - { in: query, name: until, schema: { type: string }, required: false } + - { in: query, name: user_id, schema: { type: integer }, required: false } + - { in: query, name: project_id, schema: { type: integer }, required: false } + - { in: query, name: top, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/LogStatsResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both + variants. Datetime filters are parsed server-side; pagination and top are clamped. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin /api/admin/logs/export.json: get: operationId: logs_export_json - parameters: [] + parameters: + - { in: query, name: entity_type, schema: { type: string }, required: false } + - { in: query, name: action_type, schema: { type: string }, required: false } + - { in: query, name: since, schema: { type: string }, required: false } + - { in: query, name: until, schema: { type: string }, required: false } + - { in: query, name: entity_id, schema: { type: integer }, required: false } + - { in: query, name: user_id, schema: { type: integer }, required: false } + - { in: query, name: project_id, schema: { type: integer }, required: false } + - { in: query, name: limit, schema: { type: integer }, required: false } + - { in: query, name: offset, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AdminLogList" } } } + headers: { Content-Disposition: { required: true, schema: { type: string } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both + variants. Datetime filters are parsed server-side; pagination and top are clamped. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin /api/admin/logs/cleanup: post: operationId: logs_cleanup parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { deleted: { type: integer } } + required: [ deleted ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both + variants. Datetime filters are parsed server-side; pagination and top are clamped. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { days: { type: integer, minimum: 1 } } + required: [ days ] + additionalProperties: true + x-marreq-authorization: instance-admin + x-marreq-csrf: session-csrf /api/projects/{project_id}/matrix: get: operationId: matrix_list_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/verifications/{verification_id}/matrix: - get: - operationId: matrix_get_verification_matrix - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: verification_id - required: true - schema: {type: integer} + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/MatrixLink" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every + entity belongs to the project; both variants share the shape. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements + /api/projects/{project_id}/verifications/{verification_id}/matrix: + get: + operationId: matrix_get_verification_matrix + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: verification_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { verification_id: { type: integer }, requirement_ids: { type: array, items: { type: integer } } } + required: [ verification_id, requirement_ids ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every + entity belongs to the project; both variants share the shape. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements put: operationId: matrix_put_verification_matrix parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: verification_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: verification_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + status: { type: string } + verification_id: { type: integer } + requirement_ids: { type: array, items: { type: integer } } + required: [ status, verification_id, requirement_ids ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every + entity belongs to the project; both variants share the shape. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { requirement_ids: { type: array, items: { type: integer } } } + required: [ requirement_ids ] + additionalProperties: true + x-marreq-bearer-scopes: [ traceability:write ] + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/mcp/principal: get: operationId: mcp_principal - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: [] responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/McpPrincipalResponse'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} + content: { application/json: { schema: { $ref: '#/components/schemas/McpPrincipalResponse' } } } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } /api/mcp/audit: post: operationId: mcp_audit parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user + credential; stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are + enforced. Shared in Server and Cloud. + security: [ { bearerAuth: [] } ] x-marreq-authentication-note: Personal API tokens only; delegated OAuth and sessions rejected. + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/McpAuditRequest" } } } + } + x-marreq-authorization: personal-api-token-only;project-membership + x-marreq-csrf: session-csrf /api/mcp/internal/audit: post: operationId: mcp_internal_audit parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - mcpServiceAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user + credential; stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are + enforced. Shared in Server and Cloud. + security: [ { bearerAuth: [], mcpServiceAuth: [] }, { cookieAuth: [], mcpServiceAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/McpAuditRequest" } } } + } + x-marreq-authorization: internal-mcp-service-secret;authenticated-user;project-membership + x-marreq-csrf: session-csrf /api/projects/{project_id}/me/permissions: get: operationId: members_get_my_permissions - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/EffectivePermissions" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ projects:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/members: get: operationId: members_list_members - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/MemberResponse" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/members/{user_id}: put: operationId: members_set_member_role parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: user_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: user_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/MemberResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { role: { type: integer, minimum: 1, maximum: 4 } } + required: [ role ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf delete: operationId: members_remove_member parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: user_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: user_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/reviewers: get: operationId: members_list_project_reviewers - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ projects:read ] + x-marreq-authorization: project-membership;ViewRequirements put: operationId: members_put_project_reviewers - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf /api/meta/deployment: get: operationId: meta_deployment_info @@ -1732,9 +2793,7 @@ paths: responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/DeploymentInfo'} + content: { application/json: { schema: { $ref: '#/components/schemas/DeploymentInfo' } } } /api/meta/build: get: operationId: meta_build_info @@ -1742,81 +2801,185 @@ paths: parameters: [] responses: '200': - description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/BuildInfo'} + { + description: Success, + content: { application/json: { schema: { $ref: '#/components/schemas/BuildInfo' } } } + } /api/notifications: get: operationId: notifications_list - parameters: [] + parameters: + - { in: query, name: unread_only, schema: { type: boolean }, required: false } + - { in: query, name: limit, schema: { type: integer }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Notification" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned /api/notifications/{id}/read: patch: operationId: notifications_mark_read - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { success: { type: boolean } } + required: [ success ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf /api/notifications/read-all: post: operationId: notifications_mark_all_read parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: { type: object, properties: { count: { type: integer } }, required: [ count ], additionalProperties: true } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf /api/notifications/preferences: get: operationId: notifications_get_preferences parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/NotificationPreference" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned /api/notifications/preferences/{project_id}: put: operationId: notifications_set_preference - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + notify_in_app: { anyOf: [ { type: boolean }, { type: "null" } ] } + notify_email: { anyOf: [ { type: boolean }, { type: "null" } ] } + required: [] + additionalProperties: true + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf delete: operationId: notifications_delete_preference - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf /.well-known/oauth-authorization-server: get: operationId: oauth_authorization_server_metadata @@ -1829,29 +2992,29 @@ paths: schema: type: object properties: - issuer: &id001 {type: string} + issuer: &id001 { type: string } authorization_endpoint: *id001 token_endpoint: *id001 registration_endpoint: *id001 - response_types_supported: - type: array - items: *id001 - grant_types_supported: - type: array - items: *id001 - code_challenge_methods_supported: - type: array - items: *id001 - scopes_supported: - type: array - items: *id001 - token_endpoint_auth_methods_supported: - type: array - items: *id001 - authorization_response_iss_parameter_supported: {type: boolean} - required: [issuer, authorization_endpoint, token_endpoint, registration_endpoint, response_types_supported, - grant_types_supported, code_challenge_methods_supported, scopes_supported, token_endpoint_auth_methods_supported, - authorization_response_iss_parameter_supported] + response_types_supported: { type: array, items: *id001 } + grant_types_supported: { type: array, items: *id001 } + code_challenge_methods_supported: { type: array, items: *id001 } + scopes_supported: { type: array, items: *id001 } + token_endpoint_auth_methods_supported: { type: array, items: *id001 } + authorization_response_iss_parameter_supported: { type: boolean } + required: + [ + issuer, + authorization_endpoint, + token_endpoint, + registration_endpoint, + response_types_supported, + grant_types_supported, + code_challenge_methods_supported, + scopes_supported, + token_endpoint_auth_methods_supported, + authorization_response_iss_parameter_supported + ] additionalProperties: true description: See implementation and http-api-contract.md for validation rules. security: [] @@ -1868,16 +3031,10 @@ paths: type: object properties: resource: *id001 - authorization_servers: - type: array - items: *id001 - scopes_supported: - type: array - items: *id001 - bearer_methods_supported: - type: array - items: *id001 - required: [resource, authorization_servers, scopes_supported, bearer_methods_supported] + authorization_servers: { type: array, items: *id001 } + scopes_supported: { type: array, items: *id001 } + bearer_methods_supported: { type: array, items: *id001 } + required: [ resource, authorization_servers, scopes_supported, bearer_methods_supported ] additionalProperties: true description: See implementation and http-api-contract.md for validation rules. security: [] @@ -1886,881 +3043,1823 @@ paths: operationId: oauth_register parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. + "201": + description: Success + content: + application/json: + schema: + type: object + properties: + client_id: { type: string } + client_name: { type: string } + redirect_uris: { type: array, items: { type: string } } + token_endpoint_auth_method: { type: string, const: none } + required: [ client_id, client_name, redirect_uris, token_endpoint_auth_method ] + additionalProperties: true + "400": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + "401": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "503": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + description: Audited api/oauth.rs RegistrationRequest/register/OAuthProtocolError and auth/delegated.rs. + Public PKCE client registration, shared in Server and Cloud; OAuth errors are not ApiError envelopes. security: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { client_name: { type: string }, redirect_uris: { type: array, items: { type: string } } } + required: [ client_name, redirect_uris ] + additionalProperties: true + x-marreq-authorization: public-dynamic-client-registration;redirect-uri-policy;registration-rate-limit /oauth/authorize: get: operationId: oauth_authorize_page - parameters: [] + parameters: + - { in: query, name: response_type, schema: { type: string }, required: true } + - { in: query, name: client_id, schema: { type: string }, required: true } + - { in: query, name: redirect_uri, schema: { type: string }, required: true } + - { in: query, name: scope, schema: { type: string }, required: true } + - { in: query, name: state, schema: { type: string }, required: true } + - { in: query, name: code_challenge, schema: { type: string }, required: true } + - { in: query, name: code_challenge_method, schema: { type: string }, required: true } + - { in: query, name: resource, schema: { type: string }, required: true } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. + "200": + description: HTML consent form for an authenticated browser. + content: { text/html: { schema: { type: string } } } + "303": + description: 303 redirect to login or the validated OAuth redirect_uri. + headers: { Location: { required: true, schema: { type: string } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/oauth.rs authorize_page/checked_request. Trusted redirect errors and anonymous login + return 303; consent returns HTML. Unknown clients and invalid untrusted requests return API errors. + Same in both variants. security: [] + x-marreq-authorization: validated-client-redirect-resource-pkce-scopes;optional-session-consent post: operationId: oauth_authorize_decision parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "303": + description: 303 redirect to login or the validated OAuth redirect_uri. + headers: { Location: { required: true, schema: { type: string } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/oauth.rs ConsentForm and authorize_decision. Form submissions use the session and + origin/CSRF fairing policy; both allow and deny redirect with OAuth state/issuer. Shared in both + variants. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + client_id: { type: string } + redirect_uri: { type: string } + scope: { type: string } + state: { type: string } + code_challenge: { type: string } + resource: { type: string } + decision: { type: string } + required: [ client_id, redirect_uri, scope, state, code_challenge, resource, decision ] + additionalProperties: true + x-marreq-authorization: session;validated-client-redirect-resource-pkce-scopes;consent-decision + x-marreq-csrf: auth-origin-fallback /oauth/token: post: operationId: oauth_token parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + access_token: { type: string } + token_type: { type: string, const: Bearer } + expires_in: { type: integer } + refresh_token: { type: string } + scope: { type: string } + required: [ access_token, token_type, expires_in, refresh_token, scope ] + additionalProperties: true + "400": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + "401": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "503": + description: OAuth protocol error. + content: + application/json: + schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } + description: Audited api/oauth.rs TokenForm/token and auth/delegated.rs TokenResponse. Public form endpoint + with client/resource/code/refresh checks; protocol errors use an error string. Shared in both + variants. security: [] + requestBody: + required: true + content: { application/x-www-form-urlencoded: { schema: { $ref: "#/components/schemas/OAuthTokenRequest" } } } + x-marreq-authorization: public-pkce-code-or-refresh-token;client-resource-binding;single-use-rotation /api/projects: post: operationId: projects_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_base_path: { type: string } + status: { type: string, const: ok } + required: [ id, name, slug, description, group_id, project_base_path, status ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ name ] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects + x-marreq-csrf: session-csrf get: operationId: projects_session_list_for_session parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Project" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects_session.rs and models::Project. Admin all-project view, otherwise current + memberships; bearer project scope narrows the list. Shared in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ projects:read ] + x-marreq-authorization: current-project-membership;token-project-scope /api/projects/{project_id}: patch: operationId: projects_update - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + project_base_path: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + creation_date: { anyOf: [ { type: string }, { type: "null" } ] } + update_date: { anyOf: [ { type: string }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + status: { $ref: "#/components/schemas/ProjectStatus" } + required: + - id + - name + - slug + - project_base_path + - description + - creation_date + - update_date + - owner_id + - group_id + - status + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: { anyOf: [ { type: string }, { type: "null" } ] } + description: { anyOf: [ { type: string }, { type: "null" } ] } + status: { anyOf: [ { $ref: "#/components/schemas/ProjectStatus" }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [] + additionalProperties: false + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjectConfiguration;group-ManageProjects;not-archived + x-marreq-csrf: session-csrf delete: operationId: projects_delete - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { confirm_slug: { type: string } } + required: [ confirm_slug ] + additionalProperties: false + x-marreq-authorization: session;project-owner-or-instance-admin;confirm-slug + x-marreq-csrf: session-csrf /api/projects/{project_id}/archive: post: operationId: projects_archive - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Project" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;project-owner-or-instance-admin + x-marreq-csrf: session-csrf /api/projects/{project_id}/unarchive: post: operationId: projects_unarchive - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Project" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;project-owner-or-instance-admin + x-marreq-csrf: session-csrf /api/project-from-path/{slug}: get: operationId: projects_session_project_from_path - parameters: - - in: path - name: slug - required: true - schema: {type: string} + parameters: [ { in: path, name: slug, required: true, schema: { type: string } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + id: { type: integer } + name: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + slug: { type: string } + route_slug: { type: string } + required: [ id, name, description, slug, route_slug ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/projects_session.rs project_from_path. Session-only slug lookup with membership + checks, shared in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;project-membership-or-instance-admin /api/projects/{project_id}/reports/types: get: operationId: reports_list_types - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: array + items: + type: object + properties: + key: { type: string } + title: { type: string } + description: { type: string } + formats: { type: array, items: { type: string, enum: [ pdf, odt ] } } + sections: { type: array, items: { $ref: "#/components/schemas/ReportSectionSpec" } } + default_definition: { $ref: "#/components/schemas/ReportDefinition" } + required: [ key, title, description, formats, sections, default_definition ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements /api/projects/{project_id}/report_templates: get: operationId: reports_list_templates - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/ReportTemplateView" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template post: operationId: reports_create_template - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "201": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateInput" } } } + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf /api/projects/{project_id}/report_templates/{template_id}: patch: operationId: reports_update_template parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: template_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: template_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateInput" } } } + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf delete: operationId: reports_delete_template parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: template_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: template_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf /api/projects/{project_id}/reports/{file}: post: operationId: reports_generate parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: file - required: true - schema: {type: string} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: file, required: true, schema: { type: string } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: File download; Content-Disposition supplies the filename. + headers: { Content-Disposition: { required: true, schema: { type: string } } } + content: + application/pdf: { schema: { type: string, format: binary } } + application/vnd.oasis.opendocument.text: { schema: { type: string, format: binary } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; + delegated OAuth denied. Both variants enforce project view permission, private-template visibility, + owner/admin mutation rules and archived-project write denial. Definition defaults and optional section + options follow Serde. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + template_id: { anyOf: [ { type: integer }, { type: "null" } ] } + definition: { anyOf: [ { $ref: "#/components/schemas/ReportDefinitionInput" }, { type: "null" } ] } + required: [] + additionalProperties: true + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: get: operationId: requirement_diff_diff_versions_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: v1 - required: true - schema: {type: integer} - - in: path - name: v2 - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: req_id, required: true, schema: { type: integer } } + - { in: path, name: v1, required: true, schema: { type: integer } } + - { in: path, name: v2, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementDiff" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both + versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label + fields are omitted, not null. Shared wire representation in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: get: operationId: requirement_diff_diff_baseline_vs_current parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} - - in: path - name: req_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } + - { in: path, name: req_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementDiff" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both + versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label + fields are omitted, not null. Shared wire representation in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read, baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirement-version-links: post: operationId: requirement_version_links_create parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + [ + { in: path, name: project_id, required: true, schema: { type: integer } }, + { + in: header, + name: Idempotency-Key, + required: false, + schema: { type: string }, + description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore + or hyphen. Reuse with a different payload returns 409." + } + ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionLink" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound + source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches + return 409. Both variants share this contract. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + source_version_id: { type: integer } + target_version_id: { type: integer } + link_type: { type: string } + rationale: { anyOf: [ { type: string }, { type: "null" } ] } + metadata: { anyOf: [ {}, { type: "null" } ] } + required: [ source_version_id, target_version_id, link_type ] + additionalProperties: true + x-marreq-bearer-scopes: [ traceability:write ] + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf get: operationId: requirement_version_links_list parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: source_version_id, schema: { type: integer }, required: false } + - { in: query, name: target_version_id, schema: { type: integer }, required: false } + - { in: query, name: link_type, schema: { type: string }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementVersionLink" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound + source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches + return 409. Both variants share this contract. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirement-version-links/{link_id}: delete: operationId: requirement_version_links_delete parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: link_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: link_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionLink" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound + source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches + return 409. Both variants share this contract. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:write ] + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirement-version-links/link-types: get: operationId: requirement_version_links_link_types - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { link_types: { type: array, items: { type: string } } } + required: [ link_types ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound + source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches + return 409. Both variants share this contract. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:read ] + x-marreq-authorization: project-membership;project-access /api/projects/{project_id}/requirements: get: operationId: requirements_list_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: approval_state, schema: { type: string }, required: false } + - { in: query, name: q, schema: { type: string }, required: false } + - { in: query, name: sort_column, schema: { type: string }, required: false } + - { in: query, name: sort_dir, schema: { type: string }, required: false } + - { in: query, name: status_id, schema: { type: integer }, required: false } + - { in: query, name: category_id, schema: { type: integer }, required: false } + - { in: query, name: view_id, schema: { type: integer }, required: false } + - { in: query, name: has_tests, schema: { type: boolean }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementListRow" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements post: operationId: requirements_create_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + [ + { in: path, name: project_id, required: true, schema: { type: integer } }, + { + in: header, + name: Idempotency-Key, + required: false, + schema: { type: string }, + description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore + or hyphen. Reuse with a different payload returns 409." + } + ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCreateRequest" } } } + x-marreq-bearer-scopes: [ requirements:write ] + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/requirements/{id}: delete: operationId: requirements_delete - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{id}: get: operationId: requirements_get_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementWithTraceSummary" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements patch: operationId: requirements_patch_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { success: { type: boolean }, message: { type: string } } + required: [ success, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementPatch" } } } + } + x-marreq-bearer-scopes: [ requirements:write ] + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{id}/versions: get: operationId: requirements_list_versions_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementVersion" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: get: operationId: requirements_get_version_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: req_id, required: true, schema: { type: integer } } + - { in: path, name: version_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionDetail" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirements/{id}/impacted_tests: get: operationId: requirements_get_impacted_tests_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read, verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: put: operationId: requirements_set_version_approval_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: req_id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/saved_views: - get: - operationId: saved_views_list - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: req_id, required: true, schema: { type: integer } } + - { in: path, name: version_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersion" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: { type: object, properties: { state: { type: string } }, required: [ state ], additionalProperties: true } + x-marreq-bearer-scopes: [ requirements:approve ] + x-marreq-authorization: project-membership;project-reviewer;not-archived + x-marreq-csrf: session-csrf + /api/projects/{project_id}/saved_views: + get: + operationId: saved_views_list + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] + responses: + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/SavedView" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as + serde_json::Value after service validation. Private visibility, ownership, baseline locking and + archived-project rules are enforced in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements;scoped-requirements-read post: operationId: saved_views_create - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + { + description: Success, + content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as + serde_json::Value after service validation. Private visibility, ownership, baseline locking and + archived-project rules are enforced in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/SavedViewPayload" } } } + } + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf /api/projects/{project_id}/saved_views/{view_id}: get: operationId: saved_views_get parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: view_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: view_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + { + description: Success, + content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as + serde_json::Value after service validation. Private visibility, ownership, baseline locking and + archived-project rules are enforced in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;visible-owner-or-shared patch: operationId: saved_views_update parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: view_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: view_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + { + description: Success, + content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } + } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as + serde_json::Value after service validation. Private visibility, ownership, baseline locking and + archived-project rules are enforced in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/SavedViewPayload" } } } + } + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf delete: operationId: saved_views_delete parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: view_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: view_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as + serde_json::Value after service validation. Private visibility, ownership, baseline locking and + archived-project rules are enforced in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/semantic_search: get: operationId: semantic_search_semantic_search parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: q, schema: { type: string }, required: true } + - { in: query, name: k, schema: { type: integer, minimum: 0 }, required: false } + - { in: query, name: status_filter, schema: { type: integer }, required: false } + - { in: query, name: category_filter, schema: { type: integer }, required: false } + - { in: query, name: applicability_filter, schema: { type: integer }, required: false } + - { in: query, name: verification_filter, schema: { type: integer }, required: false } + - { in: query, name: debug, schema: { type: boolean }, required: false } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + oneOf: + - type: object + properties: + enabled: { type: boolean, const: false } + message: { type: string } + results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" }, maxItems: 0 } + total: { type: integer, const: 0 } + required: [ enabled, message, results, total ] + additionalProperties: true + - type: object + properties: + enabled: { type: boolean, const: true } + results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" } } + query: { type: string } + total: { type: integer } + timing_ms: { type: integer } + required: [ enabled, results, query, total ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/semantic_search.rs and models/semantic_search.rs. Disabled and enabled payloads + differ explicitly; timing_ms and rank details are optional. Both variants use runtime embedding + configuration. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership /api/projects/{project_id}/requirements/ask: post: operationId: semantic_search_ask - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RagAnswerResponse" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/semantic_search.rs ask and RagAnswerResponse. RAG-disabled state returns 400, + provider/repository failures map to API errors. Shared in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + query: { type: string } + k: { anyOf: [ { type: integer, minimum: 0 }, { type: "null" } ] } + status_filter: { anyOf: [ { type: integer }, { type: "null" } ] } + category_filter: { anyOf: [ { type: integer }, { type: "null" } ] } + applicability_filter: { anyOf: [ { type: integer }, { type: "null" } ] } + verification_filter: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ query ] + additionalProperties: true + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/index_status: get: operationId: semantic_search_index_status - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] + responses: { default: { description: Wire behavior has not yet been verified. } } x-marreq-unverified: true description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] /api/status: get: operationId: status_list_requirement_statuses parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementStatus" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ViewRequirements post: operationId: status_create_requirement_status parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "201": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewRequirementStatus" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/status/{id}: put: operationId: status_update_requirement_status - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewRequirementStatus" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf delete: operationId: status_delete_requirement_status - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{id}/trace_up: get: operationId: traceability_trace_up parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + parent: { anyOf: [ { $ref: "#/components/schemas/Requirement" }, { type: "null" } ] } + parents: { type: array, items: { $ref: "#/components/schemas/Requirement" } } + required: [ parent, parents ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses + retain nullable first parent, complete parent arrays and explicit no_change result, identically in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read, traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/requirements/{id}/trace_down: get: operationId: traceability_trace_down parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + child_requirements: { type: array, items: { $ref: "#/components/schemas/Requirement" } } + linked_tests: { type: array, items: { $ref: "#/components/schemas/Verification" } } + required: [ child_requirements, linked_tests ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses + retain nullable first parent, complete parent arrays and explicit no_change result, identically in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read, verifications:read, traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/coverage_report: get: operationId: traceability_coverage_report - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + requirements_without_tests: { type: array, items: { type: integer } } + tests_without_requirements: { type: array, items: { type: integer } } + suspect_links: + type: array + items: + type: object + properties: { req_id: { type: integer }, verification_id: { type: integer } } + required: [ req_id, verification_id ] + additionalProperties: true + required: [ requirements_without_tests, tests_without_requirements, suspect_links ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses + retain nullable first parent, complete parent arrays and explicit no_change result, identically in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ traceability:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/traceability/clear_suspect: post: operationId: traceability_clear_suspect parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, enum: [ ok, no_change ] }, cleared: { type: boolean } } + required: [ status, cleared ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses + retain nullable first parent, complete parent arrays and explicit no_change result, identically in + both variants. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { req_id: { type: integer }, verification_id: { type: integer } } + required: [ req_id, verification_id ] + additionalProperties: true + x-marreq-authorization: EditRequirements;project-membership;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/traceability/clear_suspect: post: operationId: traceability_clear_suspect_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/use-cases: + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, enum: [ ok, no_change ] }, cleared: { type: boolean } } + required: [ status, cleared ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses + retain nullable first parent, complete parent arrays and explicit no_change result, identically in + both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { req_id: { type: integer }, verification_id: { type: integer } } + required: [ req_id, verification_id ] + additionalProperties: true + x-marreq-bearer-scopes: [ traceability:write ] + x-marreq-authorization: EditRequirements;project-membership;not-archived + x-marreq-csrf: session-csrf + /api/projects/{project_id}/use-cases: get: operationId: use_cases_list - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: query - name: q - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} - - in: query - name: approval_state - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} - - in: query - name: actor - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} - - in: query - name: has_suspect - required: false - schema: - anyOf: - - {type: boolean} - - {type: 'null'} - - in: query - name: requirement_id - required: false - schema: - anyOf: - - {type: integer} - - {type: 'null'} - - in: query - name: verification_id - required: false - schema: - anyOf: - - {type: integer} - - {type: 'null'} - - in: query - name: sort_column - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} - - in: query - name: sort_dir - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: query, name: q, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } + - in: query + name: approval_state + required: false + schema: { anyOf: [ { type: string }, { type: 'null' } ] } + - { in: query, name: actor, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } + - { + in: query, + name: has_suspect, + required: false, + schema: { anyOf: [ { type: boolean }, { type: 'null' } ] } + } + - in: query + name: requirement_id + required: false + schema: { anyOf: [ { type: integer }, { type: 'null' } ] } + - in: query + name: verification_id + required: false + schema: { anyOf: [ { type: integer }, { type: 'null' } ] } + - { + in: query, + name: sort_column, + required: false, + schema: { anyOf: [ { type: string }, { type: 'null' } ] } + } + - { in: query, name: sort_dir, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_UseCaseRow_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRow_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. post: operationId: use_cases_create parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: header - name: Idempotency-Key - required: false - schema: &id002 {type: string} - description: Safe replay for the same principal, target, operation and payload; changed payload returns - 409. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - in: header + name: Idempotency-Key + required: false + schema: &id002 { type: string } + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. responses: '201': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_UseCaseDetail_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseDetail_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } description: Create the shared draft; optional reference_code, title, primary_actor and document. A missing code is generated. Supports Idempotency-Key. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:write'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:write' ] x-marreq-csrf: session-csrf requestBody: required: true @@ -2769,273 +4868,254 @@ paths: schema: type: object properties: - reference_code: - type: [string, 'null'] - title: - type: [string, 'null'] - primary_actor: - type: [string, 'null'] - document: - anyOf: - - {$ref: '#/components/schemas/UseCaseDocument'} - - {type: 'null'} + reference_code: { type: [ string, 'null' ] } + title: { type: [ string, 'null' ] } + primary_actor: { type: [ string, 'null' ] } + document: { anyOf: [ { $ref: '#/components/schemas/UseCaseDocument' }, { type: 'null' } ] } additionalProperties: true /api/projects/{project_id}/use-cases/next-code: get: operationId: use_cases_next_code - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_NextCode_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_NextCode_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}: get: operationId: use_cases_get - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: query - name: version_id - required: false - schema: - anyOf: - - {type: integer} - - {type: 'null'} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { + in: query, + name: version_id, + required: false, + schema: { anyOf: [ { type: integer }, { type: 'null' } ] } + } responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_UseCaseDetail_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseDetail_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. delete: operationId: use_cases_delete - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': {description: Deleted} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': { description: Deleted } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: ['use_cases:write'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + x-marreq-bearer-scopes: [ 'use_cases:write' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/draft: put: operationId: use_cases_put_draft - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftChangeView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf requestBody: required: true - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_PutDraftRequest_request'} - x-marreq-bearer-scopes: ['use_cases:write'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_PutDraftRequest_request' } } } + x-marreq-bearer-scopes: [ 'use_cases:write' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. delete: operationId: use_cases_discard_draft - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': {description: Deleted} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': { description: Deleted } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: ['use_cases:write'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + x-marreq-bearer-scopes: [ 'use_cases:write' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. get: operationId: use_cases_get_draft - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_DraftView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/draft/operations: post: operationId: use_cases_draft_operations parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_DraftChangeView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftChangeView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:write'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:write' ] requestBody: required: true content: @@ -3043,222 +5123,181 @@ paths: schema: type: object properties: - base_revision: - anyOf: - - &id003 {type: integer} - - {type: 'null'} - operations: - type: array - items: {$ref: '#/components/schemas/UseCaseOperation'} + base_revision: { anyOf: [ &id003 { type: integer }, { type: 'null' } ] } + operations: { type: array, items: { $ref: '#/components/schemas/UseCaseOperation' } } additionalProperties: true - required: [operations] + required: [ operations ] x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/draft/publish: post: operationId: use_cases_publish parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: header - name: Idempotency-Key - required: false - schema: *id002 - description: Safe replay for the same principal, target, operation and payload; changed payload returns - 409. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - in: header + name: Idempotency-Key + required: false + schema: *id002 + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. responses: '201': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/UseCasePublishResponse'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/UseCasePublishResponse' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:write'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:write' ] requestBody: required: true content: application/json: schema: type: object - properties: - base_revision: *id003 - change_note: - anyOf: - - *id002 - - {type: 'null'} + properties: { base_revision: *id003, change_note: { anyOf: [ *id002, { type: 'null' } ] } } additionalProperties: true - required: [base_revision] + required: [ base_revision ] x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/versions: get: operationId: use_cases_list_versions - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_VersionSummary_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_VersionSummary_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: put: operationId: use_cases_set_version_approval - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: version_id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: path, name: version_id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_VersionView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_VersionView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf requestBody: required: true - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_SetApprovalRequest_request'} - x-marreq-bearer-scopes: ['use_cases:approve'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_SetApprovalRequest_request' } } } + x-marreq-bearer-scopes: [ 'use_cases:approve' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/links: get: operationId: use_cases_list_links - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_LinkView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_LinkView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. post: operationId: use_cases_create_link parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: '201': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_LinkView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_LinkView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:write'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:write' ] requestBody: required: true content: @@ -3266,349 +5305,275 @@ paths: schema: type: object properties: - element_id: - anyOf: - - *id002 - - {type: 'null'} - requirement_id: - anyOf: - - *id003 - - {type: 'null'} - verification_id: - anyOf: - - *id003 - - {type: 'null'} - rationale: - anyOf: - - *id002 - - {type: 'null'} + element_id: { anyOf: [ *id002, { type: 'null' } ] } + requirement_id: { anyOf: [ *id003, { type: 'null' } ] } + verification_id: { anyOf: [ *id003, { type: 'null' } ] } + rationale: { anyOf: [ *id002, { type: 'null' } ] } additionalProperties: true x-marreq-csrf: session-csrf /api/projects/{project_id}/use-cases/{id}/links/{link_id}: delete: operationId: use_cases_delete_link - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: link_id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': {description: Deleted} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: path, name: link_id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': { description: Deleted } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: ['use_cases:write'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + x-marreq-bearer-scopes: [ 'use_cases:write' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: post: operationId: use_cases_clear_link_suspect - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: link_id - required: true - schema: {type: integer} - - in: header - name: X-CSRF-Token - required: false - schema: {type: string} - description: Required for session mutations; Bearer requests use scoped authentication instead. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: path, name: link_id, required: true, schema: { type: integer } } + - in: header + name: X-CSRF-Token + required: false + schema: { type: string } + description: Required for session mutations; Bearer requests use scoped authentication instead. responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_ClearSuspectResponse_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_ClearSuspectResponse_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: ['use_cases:write'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + x-marreq-bearer-scopes: [ 'use_cases:write' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/requirements/{requirement_id}/use-cases: get: operationId: use_cases_requirement_use_cases - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: requirement_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: requirement_id, required: true, schema: { type: integer } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_UseCaseRef_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRef_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/verifications/{verification_id}/use-cases: get: operationId: use_cases_verification_use_cases - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: verification_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: verification_id, required: true, schema: { type: integer } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_UseCaseRef_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRef_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/projects/{project_id}/use-cases/{id}/draft/diff: get: operationId: use_cases_draft_diff parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: query - name: include_unchanged - required: false - schema: &id004 {type: boolean} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: query, name: include_unchanged, required: false, schema: &id004 { type: boolean } } responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/UseCaseDiff'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/UseCaseDiff' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:read'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:read' ] /api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: get: operationId: use_cases_version_diff parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: a - required: true - schema: {type: integer} - - in: path - name: b - required: true - schema: {type: integer} - - in: query - name: include_unchanged - required: false - schema: *id004 + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: path, name: a, required: true, schema: { type: integer } } + - { in: path, name: b, required: true, schema: { type: integer } } + - { in: query, name: include_unchanged, required: false, schema: *id004 } responses: '200': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/UseCaseDiff'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/UseCaseDiff' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:read'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:read' ] /api/projects/{project_id}/use-cases/{id}/comments: get: operationId: use_cases_list_comments - security: - - cookieAuth: [] - - bearerAuth: [] - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: query - name: version_id - required: false - schema: - anyOf: - - {type: integer} - - {type: 'null'} - - in: query - name: element_id - required: false - schema: - anyOf: - - {type: string} - - {type: 'null'} + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { + in: query, + name: version_id, + required: false, + schema: { anyOf: [ { type: integer }, { type: 'null' } ] } + } + - { in: query, name: element_id, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_UseCaseCommentView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseCommentView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. post: operationId: use_cases_create_comment parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: header - name: Idempotency-Key - required: false - schema: *id002 - description: Safe replay for the same principal, target, operation and payload; changed payload returns - 409. + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - in: header + name: Idempotency-Key + required: false + schema: *id002 + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. responses: '201': description: Success - content: - application/json: - schema: {$ref: '#/components/schemas/use_cases_UseCaseCommentView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '403': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: Guard/fairing/validation failure; body may be empty or framework generated} - '500': {$ref: '#/components/responses/ApiError'} + content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseCommentView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, idempotency, publishing, links and review permissions. - security: - - cookieAuth: [] - - bearerAuth: [] - x-marreq-bearer-scopes: ['use_cases:write'] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ 'use_cases:write' ] requestBody: required: true content: @@ -3617,499 +5582,931 @@ paths: type: object properties: body: *id002 - use_case_version_id: - anyOf: - - *id003 - - {type: 'null'} - element_id: - anyOf: - - *id002 - - {type: 'null'} + use_case_version_id: { anyOf: [ *id003, { type: 'null' } ] } + element_id: { anyOf: [ *id002, { type: 'null' } ] } additionalProperties: true - required: [body] + required: [ body ] x-marreq-csrf: session-csrf /api/projects/{project_id}/baselines/{baseline_id}/use-cases: get: operationId: use_cases_baseline_use_cases - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } responses: '200': description: Success content: - application/json: - schema: - type: array - items: {$ref: '#/components/schemas/use_cases_BaselineUseCaseView_response'} - '400': {$ref: '#/components/responses/ApiError'} - '401': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '403': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '404': {$ref: '#/components/responses/ApiError'} - '409': {$ref: '#/components/responses/ApiError'} - '422': {description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty - or framework generated; do not require an ApiError body.'} - '500': {$ref: '#/components/responses/ApiError'} - x-marreq-bearer-scopes: ['use_cases:read'] - description: Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review - permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and revision-conflict - semantics. + application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_BaselineUseCaseView_response' } } } + '400': { $ref: '#/components/responses/ApiError' } + '401': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '403': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '404': { $ref: '#/components/responses/ApiError' } + '409': { $ref: '#/components/responses/ApiError' } + '422': + { + description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework + generated; do not require an ApiError body.' + } + '500': { $ref: '#/components/responses/ApiError' } + x-marreq-bearer-scopes: [ 'use_cases:read' ] + description: Session or scoped Bearer authentication; project membership and + ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See + use-cases-api.md for validation and revision-conflict semantics. /api/users/{id}: put: operationId: users_update - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/entities_User_response" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud + returns 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: [ { cookieAuth: [] } ] + requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UpdateUser" } } } } + x-marreq-authorization: instance-admin;deployment-admin-promotion-policy + x-marreq-csrf: session-csrf delete: operationId: users_delete - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud + returns 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin;deployment-admin-deletion-policy + x-marreq-csrf: session-csrf /api/users/{id}/password: put: operationId: users_set_password - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud + returns 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { new_password: { type: string }, confirm_password: { type: string } } + required: [ new_password, confirm_password ] + additionalProperties: true + x-marreq-authorization: instance-admin + x-marreq-csrf: session-csrf /api/projects/{project_id}/verifications/{verification_id}/control: get: operationId: verification_control_get_verification_control parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: verification_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: verification_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs + are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements put: operationId: verification_control_put_verification_control parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: verification_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: verification_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs + are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlInput" } } } + x-marreq-bearer-scopes: [ verifications:write ] + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/requirements/{requirement_id}/close_out: get: operationId: verification_control_get_requirement_close_out parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: requirement_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: requirement_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCloseOutView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs + are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/close_out: get: operationId: verification_control_get_project_close_out - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - /api/projects/{project_id}/requirements/{requirement_id}/compliance: - put: - operationId: verification_control_put_requirement_compliance - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: requirement_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: { schema: { type: object, additionalProperties: { $ref: "#/components/schemas/RequirementCloseOutView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs + are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ requirements:read ] + x-marreq-authorization: project-membership;ViewRequirements + /api/projects/{project_id}/requirements/{requirement_id}/compliance: + put: + operationId: verification_control_put_requirement_compliance + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: requirement_id, required: true, schema: { type: integer } } + responses: + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCloseOutView" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs + are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + compliance: { anyOf: [ { type: string }, { type: "null" } ] } + note: { anyOf: [ { type: string }, { type: "null" } ] } + required: [] + additionalProperties: false + x-marreq-bearer-scopes: [ requirements:write ] + x-marreq-authorization: project-membership;EditRequirements;project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/verifications/{id}/snapshots: get: operationId: verification_diff_list_snapshots_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: { type: integer } + created_at: { type: string } + name: { type: string } + reference_code: { type: string } + status_id: { type: integer } + required: [ id, created_at, name, reference_code, status_id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both + versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label + fields are omitted, not null. Shared wire representation in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: get: operationId: verification_diff_diff_snapshots_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} - - in: path - name: v1 - required: true - schema: {type: integer} - - in: path - name: v2 - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } + - { in: path, name: v1, required: true, schema: { type: integer } } + - { in: path, name: v2, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/VerificationVersionDiff" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both + versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label + fields are omitted, not null. Shared wire representation in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: get: operationId: verification_diff_diff_baseline_vs_current parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: baseline_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: baseline_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/VerificationVersionDiff" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both + versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label + fields are omitted, not null. Shared wire representation in both variants. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read, baselines:read ] + x-marreq-authorization: project-membership;ViewRequirements /api/projects/{project_id}/verification-methods: get: operationId: verification_methods_list_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/VerificationMethod" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: ViewRequirements post: operationId: verification_methods_create_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationMethod" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/verification-methods/{method_id}: put: operationId: verification_methods_update_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: method_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: method_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationMethod" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf delete: operationId: verification_methods_delete_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: method_id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: method_id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/verification-status: get: operationId: verification_status_list_verification_statuses parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/VerificationStatus" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ViewRequirements post: operationId: verification_status_create_verification_status parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "201": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationStatus" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/verification-status/{id}: put: operationId: verification_status_update_verification_status - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] } ] + requestBody: + required: true + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationStatus" } } } + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf delete: operationId: verification_status_delete_verification_status - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud + wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System + status mutations are rejected. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf /api/verifications: get: operationId: verifications_list parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;ViewRequirements post: operationId: verifications_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } + } + x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/verifications: get: operationId: verifications_list_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements post: operationId: verifications_create_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} + [ + { in: path, name: project_id, required: true, schema: { type: integer } }, + { + in: header, + name: Idempotency-Key, + required: false, + schema: { type: string }, + description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore + or hyphen. Reuse with a different payload returns 409." + } + ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } + } + x-marreq-bearer-scopes: [ verifications:write ] + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/verifications/{id}: get: operationId: verifications_get_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] - put: - operationId: verifications_update_by_project - parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + x-marreq-bearer-scopes: [ verifications:read ] + x-marreq-authorization: project-membership;ViewRequirements + put: + operationId: verifications_update_by_project + parameters: + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } + } + x-marreq-bearer-scopes: [ verifications:write ] + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/verifications/{id}: get: operationId: verifications_get - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;ViewRequirements delete: operationId: verifications_delete - parameters: - - in: path - name: id - required: true - schema: {type: integer} + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] + "204": { description: Success; empty body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/projects/{project_id}/verifications/{id}/field: post: operationId: verifications_update_field_by_project parameters: - - in: path - name: project_id - required: true - schema: {type: integer} - - in: path - name: id - required: true - schema: {type: integer} + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: id, required: true, schema: { type: integer } } responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: - - cookieAuth: [] - - bearerAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { success: { type: boolean }, message: { type: string } } + required: [ success, message ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants + use the same serializers. Scoped routes check project boundaries and scopes; mutations require + EditRequirements and unarchived projects, with project-reviewer checks for status/approval + transitions. Idempotent creates reject conflicting payloads with 409. + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: { field: { type: string }, value: { type: string } } + required: [ field, value ] + additionalProperties: true + x-marreq-bearer-scopes: [ verifications:write ] + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf /api/auth/csrf: get: operationId: auth_auth_csrf @@ -4122,41 +6519,28 @@ paths: application/json: schema: type: object - properties: - csrf_token: {type: string} - required: [csrf_token] + properties: { csrf_token: { type: string } } + required: [ csrf_token ] additionalProperties: true /api/auth/identities/{identity_id}: delete: operationId: auth_auth_identity_delete - parameters: - - in: path - name: identity_id - required: true - schema: - type: integer - responses: - "204": - description: Owned identity removed; no body. - "400": - $ref: "#/components/responses/ApiError" - "401": - $ref: "#/components/responses/ApiError" - "403": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + parameters: [ { in: path, name: identity_id, required: true, schema: { type: integer } } ] + responses: + "204": { description: Owned identity removed; no body. } + "400": { $ref: "#/components/responses/ApiError" } + "401": { $ref: "#/components/responses/ApiError" } + "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-csrf: session-csrf /api/auth/register: post: operationId: auth_public_auth_register - x-marreq-variants: [cloud] + x-marreq-variants: [ cloud ] parameters: [] responses: '201': @@ -4165,9 +6549,8 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - required: [status] + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true '200': description: Success @@ -4175,12 +6558,11 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - required: [status] + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true - '400': {$ref: '#/components/responses/ApiError'} - '500': {$ref: '#/components/responses/ApiError'} + '400': { $ref: '#/components/responses/ApiError' } + '500': { $ref: '#/components/responses/ApiError' } description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. security: [] @@ -4191,11 +6573,11 @@ paths: schema: type: object properties: - username: &id005 {type: string} + username: &id005 { type: string } name: *id005 email: *id005 password: *id005 - required: [username, name, email, password] + required: [ username, name, email, password ] additionalProperties: true /api/auth/login: post: @@ -4208,15 +6590,13 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - user: {$ref: '#/components/schemas/entities_User_response'} - required: [status, user] - '400': {$ref: '#/components/responses/ApiError'} - '403': {description: CSRF failure; body may be framework generated} - '410': {$ref: '#/components/responses/ApiError'} - '422': {description: Rocket JSON validation failure} - '500': {$ref: '#/components/responses/ApiError'} + properties: { status: { type: string, const: ok }, user: { $ref: '#/components/schemas/entities_User_response' } } + required: [ status, user ] + '400': { $ref: '#/components/responses/ApiError' } + '403': { description: CSRF failure; body may be framework generated } + '410': { $ref: '#/components/responses/ApiError' } + '422': { description: Rocket JSON validation failure } + '500': { $ref: '#/components/responses/ApiError' } description: Route inventory only. Request/response serialization, status codes and authorization require audit before compatibility can be established. security: [] @@ -4227,79 +6607,99 @@ paths: application/json: schema: type: object - required: [username, password] - properties: - username: {type: string} - password: {type: string} + required: [ username, password ] + properties: { username: { type: string }, password: { type: string } } /api/users: post: operationId: users_create parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: { status: { type: string, const: ok }, id: { type: integer } } + required: [ status, id ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "409": { $ref: "#/components/responses/ApiError" } + "410": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud + returns 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: [ { cookieAuth: [] } ] + requestBody: + { + required: true, + content: { application/json: { schema: { $ref: "#/components/schemas/UserCreateRequest" } } } + } + x-marreq-authorization: instance-admin;allows_self_administered_user_creation + x-marreq-csrf: session-csrf get: operationId: users_list parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: { schema: { type: array, items: { $ref: "#/components/schemas/entities_User_response" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud + returns 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: instance-admin /api/projects/{project_id}/attachments/{attachment_id}: delete: operationId: attachments_delete parameters: - - in: path - name: project_id - required: true - schema: - type: integer - - in: path - name: attachment_id - required: true - schema: - type: integer + - { in: path, name: project_id, required: true, schema: { type: integer } } + - { in: path, name: attachment_id, required: true, schema: { type: integer } } responses: - "204": - description: Attachment deleted; empty body. Baseline retention can keep the stored bytes. - "400": - $ref: "#/components/responses/ApiError" + "204": { description: Attachment deleted; empty body. Baseline retention can keep the stored bytes. } + "400": { $ref: "#/components/responses/ApiError" } "401": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. "403": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } "503": description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: - - cookieAuth: [] - - bearerAuth: [] + security: [ { cookieAuth: [] }, { bearerAuth: [] } ] x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied x-marreq-csrf: session-csrf /api/auth/external/{provider}/link: post: operationId: auth_auth_external_link_start - parameters: - - in: path - name: provider - required: true - schema: - type: string + parameters: [ { in: path, name: provider, required: true, schema: { type: string } } ] responses: "200": description: Success @@ -4307,47 +6707,31 @@ paths: application/json: schema: type: object - properties: - authorization_url: - type: string - required: - - authorization_url + properties: { authorization_url: { type: string } } + required: [ authorization_url ] additionalProperties: true - "400": - $ref: "#/components/responses/ApiError" - "401": - $ref: "#/components/responses/ApiError" - "403": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "404": - $ref: "#/components/responses/ApiError" - "422": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "500": - $ref: "#/components/responses/ApiError" + "400": { $ref: "#/components/responses/ApiError" } + "401": { $ref: "#/components/responses/ApiError" } + "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "404": { $ref: "#/components/responses/ApiError" } + "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] requestBody: required: true content: application/json: schema: description: Any JSON value is accepted; an object return_to string overrides the default /settings/account. - properties: - return_to: - type: string + properties: { return_to: { type: string } } x-marreq-csrf: session-csrf /api/auth/verify-email: get: operationId: auth_public_auth_verify_email - x-marreq-variants: [cloud] - parameters: - - in: query - name: token - required: true - schema: *id005 + x-marreq-variants: [ cloud ] + parameters: [ { in: query, name: token, required: true, schema: *id005 } ] responses: '200': description: Success @@ -4355,12 +6739,11 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - required: [status] + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true - '400': {$ref: '#/components/responses/ApiError'} - '500': {$ref: '#/components/responses/ApiError'} + '400': { $ref: '#/components/responses/ApiError' } + '500': { $ref: '#/components/responses/ApiError' } description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. security: [] @@ -4372,14 +6755,9 @@ paths: '200': description: Session and CSRF cookies cleared content: - application/json: - schema: - type: object - properties: - status: {type: string, const: ok} - required: [status] - '403': {description: CSRF failure; body may be framework generated} - '500': {$ref: '#/components/responses/ApiError'} + application/json: { schema: { type: object, properties: { status: { type: string, const: ok } }, required: [ status ] } } + '403': { description: CSRF failure; body may be framework generated } + '500': { $ref: '#/components/responses/ApiError' } description: Route inventory only. Request/response serialization, status codes and authorization require audit before compatibility can be established. security: [] @@ -4387,13 +6765,11 @@ paths: requestBody: required: false description: Optional ignored body; the SPA sends an empty JSON object. - content: - application/json: - schema: {} + content: { application/json: { schema: {} } } /api/auth/forgot-password: post: operationId: auth_public_auth_forgot_password - x-marreq-variants: [cloud] + x-marreq-variants: [ cloud ] parameters: [] responses: '200': @@ -4402,12 +6778,11 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - required: [status] + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true - '400': {$ref: '#/components/responses/ApiError'} - '500': {$ref: '#/components/responses/ApiError'} + '400': { $ref: '#/components/responses/ApiError' } + '500': { $ref: '#/components/responses/ApiError' } description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. security: [] @@ -4415,54 +6790,53 @@ paths: required: true content: application/json: - schema: - type: object - properties: - email: *id005 - required: [email] - additionalProperties: true + schema: { type: object, properties: { email: *id005 }, required: [ email ], additionalProperties: true } /api/notifications/unread-count: get: operationId: notifications_unread_count parameters: [] - responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] - /api/auth/change-password: - post: - operationId: auth_auth_change_password - parameters: [] responses: "200": description: Success content: application/json: - schema: - $ref: "#/components/schemas/AuthStatus" - "400": - $ref: "#/components/responses/ApiError" + schema: { type: object, properties: { count: { type: integer } }, required: [ count ], additionalProperties: true } + "400": { $ref: "#/components/responses/ApiError" } "401": - $ref: "#/components/responses/ApiError" + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. "403": - description: Authentication or CSRF failure; body may be framework-generated or empty. + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } "422": - description: Authentication or CSRF failure; body may be framework-generated or empty. - "500": - $ref: "#/components/responses/ApiError" + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped + to the current user in both variants. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-owned + /api/auth/change-password: + post: + operationId: auth_auth_change_password + parameters: [] + responses: + "200": + description: Success + content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } + "400": { $ref: "#/components/responses/ApiError" } + "401": { $ref: "#/components/responses/ApiError" } + "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and Cloud. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] requestBody: required: true - content: - application/json: - schema: - $ref: "#/components/schemas/AuthChangePassword" + content: { application/json: { schema: { $ref: "#/components/schemas/AuthChangePassword" } } } x-marreq-csrf: session-csrf /api/oauth/grants: get: @@ -4475,84 +6849,82 @@ paths: application/json: schema: type: object - required: - - grants + required: [ grants ] properties: grants: type: array items: type: object - required: - - id - - application_name - - scopes - - created_at - - last_used_at + required: [ id, application_name, scopes, created_at, last_used_at ] properties: - id: - type: integer - application_name: - type: string - scopes: - type: array - items: - type: string - created_at: - type: string - last_used_at: - type: - - string - - "null" + id: { type: integer } + application_name: { type: string } + scopes: { type: array, items: { type: string } } + created_at: { type: string } + last_used_at: { type: [ string, "null" ] } additionalProperties: true additionalProperties: true - "401": - description: Missing or expired session; guard response may be empty. - "500": - $ref: "#/components/responses/ApiError" + "401": { description: Missing or expired session; guard response may be empty. } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants only, identical in Server and Cloud; no Bearer authentication. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] /api/oauth/grants/{id}: delete: operationId: oauth_revoke_grant - parameters: - - in: path - name: id - required: true - schema: - type: integer + parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] responses: - "204": - description: Owned grant revoked; empty body. - "401": - description: Missing or expired session; guard response may be empty. - "403": - description: CSRF failure; body may be framework-generated. - "404": - $ref: "#/components/responses/ApiError" - "500": - $ref: "#/components/responses/ApiError" + "204": { description: Owned grant revoked; empty body. } + "401": { description: Missing or expired session; guard response may be empty. } + "403": { description: CSRF failure; body may be framework-generated. } + "404": { $ref: "#/components/responses/ApiError" } + "500": { $ref: "#/components/responses/ApiError" } description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants only, identical in Server and Cloud; no Bearer authentication. - security: - - cookieAuth: [] + security: [ { cookieAuth: [] } ] x-marreq-csrf: session-csrf /api/dashboard: get: operationId: dashboard_dashboard_json parameters: [] responses: - default: {description: Wire behavior has not yet been verified.} - x-marreq-unverified: true - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: - - cookieAuth: [] + "200": + description: Success + content: + application/json: + schema: + type: object + properties: + user: { $ref: "#/components/schemas/entities_User_response" } + projects: { type: array, items: { $ref: "#/components/schemas/DashboardProject" } } + projects_count: { type: integer } + selected_project_id: { anyOf: [ { type: integer }, { type: "null" } ] } + selected_project_slug: { anyOf: [ { type: string }, { type: "null" } ] } + csrf_token: { type: string } + required: [ user, projects, projects_count, selected_project_id, selected_project_slug, csrf_token ] + additionalProperties: true + "400": { $ref: "#/components/responses/ApiError" } + "401": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "403": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "404": { $ref: "#/components/responses/ApiError" } + "409": { $ref: "#/components/responses/ApiError" } + "422": + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or + framework-generated. + "500": { $ref: "#/components/responses/ApiError" } + description: Audited api/dashboard.rs and routes/helpers.rs decorate_projects_for_listing. Project cards + include the exact decoration keys; nulls reflect absent selection/metadata. Both variants share this + session payload. + security: [ { cookieAuth: [] } ] + x-marreq-authorization: session-user-project-memberships /api/auth/reset-password: post: operationId: auth_public_auth_reset_password - x-marreq-variants: [cloud] + x-marreq-variants: [ cloud ] parameters: [] responses: '200': @@ -4561,12 +6933,11 @@ paths: application/json: schema: type: object - properties: - status: {type: string, const: ok} - required: [status] + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true - '400': {$ref: '#/components/responses/ApiError'} - '500': {$ref: '#/components/responses/ApiError'} + '400': { $ref: '#/components/responses/ApiError' } + '500': { $ref: '#/components/responses/ApiError' } description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. security: [] @@ -4576,50 +6947,1454 @@ paths: application/json: schema: type: object - properties: - token: *id005 - new_password: *id005 - required: [token, new_password] + properties: { token: *id005, new_password: *id005 } + required: [ token, new_password ] additionalProperties: true components: securitySchemes: - cookieAuth: {type: apiKey, in: cookie, name: session, description: Rocket private session; __Host-session on - secure deployments. Mutations require X-CSRF-Token.} - bearerAuth: {type: http, scheme: bearer, description: 'Personal API token or delegated OAuth access token; project - membership, domain scopes and project permissions apply.'} - mcpServiceAuth: {type: apiKey, in: header, name: X-Marreq-Mcp-Audit-Secret, description: Private service-to-service - audit secret; never browser or delegated caller configuration.} + cookieAuth: + { + type: apiKey, + in: cookie, + name: session, + description: Rocket private session; __Host-session on secure deployments. Mutations require X-CSRF-Token. + } + bearerAuth: + { + type: http, + scheme: bearer, + description: 'Personal API token or delegated OAuth access token; project membership, domain scopes and + project permissions apply.' + } + mcpServiceAuth: + { + type: apiKey, + in: header, + name: X-Marreq-Mcp-Audit-Secret, + description: Private service-to-service audit secret; never browser or delegated caller configuration. + } schemas: - AttachmentView: + SemanticSearchResult: + type: object + properties: + id: { type: integer } + rank: { type: integer } + lexical_rank: { type: integer } + vector_rank: { type: integer } + reference_code: { type: string } + title: { type: string } + description: { type: string } + snippet: { type: string } + status: { type: string } + category: { type: string } + applicability: { type: string } + verification: { type: string } + score: { type: number } + required: + - id + - rank + - reference_code + - title + - description + - snippet + - status + - category + - applicability + - verification + - score + additionalProperties: true + RagAnswerResponse: + type: object + properties: + answer: { type: string } + citations: + type: array + items: + type: object + properties: { requirement_id: { type: integer }, reference_code: { type: string }, title: { type: string } } + required: [ requirement_id, reference_code, title ] + additionalProperties: true + results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" } } + required: [ answer, citations, results ] + additionalProperties: true + OAuthTokenRequest: + oneOf: + - type: object + properties: + client_id: { type: string } + resource: { type: string } + grant_type: { type: string, const: authorization_code } + code: { type: string } + redirect_uri: { type: string } + code_verifier: { type: string } + refresh_token: { type: string } + required: [ grant_type, client_id, resource, code, redirect_uri, code_verifier ] + additionalProperties: true + - type: object + properties: + client_id: { type: string } + resource: { type: string } + grant_type: { type: string, const: refresh_token } + refresh_token: { type: string } + code: { type: string } + redirect_uri: { type: string } + code_verifier: { type: string } + required: [ grant_type, client_id, resource, refresh_token ] + additionalProperties: true + ReportDefinition: + type: object + properties: + report_type: { type: string, enum: [ vcd, coverage ] } + document: + type: object + properties: + doc_id: { type: string } + title: { type: string } + issue: { type: string } + revision: { type: string } + classification: { type: string } + watermark: { anyOf: [ { type: string }, { type: "null" } ] } + page_size: { type: string, enum: [ a4, letter ] } + pdf_a: { type: boolean } + signatories: + type: array + items: + type: object + properties: { role: { type: string }, name: { type: string } } + required: [ role, name ] + additionalProperties: true + change_record: + type: array + items: + type: object + properties: { issue: { type: string }, date: { type: string }, changes: { type: string }, author: { type: string } } + required: [ issue, date, changes, author ] + additionalProperties: true + documents: + type: array + items: + type: object + properties: { ref: { type: string }, id: { type: string }, title: { type: string }, issue: { type: string } } + required: [ ref, id, title, issue ] + additionalProperties: true + required: + - doc_id + - title + - issue + - revision + - classification + - watermark + - page_size + - pdf_a + - signatories + - change_record + - documents + additionalProperties: true + sections: + type: array + items: + type: object + properties: { key: { type: string }, enabled: { type: boolean }, options: { type: object, additionalProperties: true } } + required: [ key, enabled ] + additionalProperties: true + required: [ report_type, document, sections ] + additionalProperties: true + ReportDefinitionInput: + type: object + properties: + report_type: { type: string, enum: [ vcd, coverage ] } + document: + type: object + properties: + doc_id: { type: string } + title: { type: string } + issue: { type: string } + revision: { type: string } + classification: { type: string } + watermark: { anyOf: [ { type: string }, { type: "null" } ] } + page_size: { type: string, enum: [ a4, letter ] } + pdf_a: { type: boolean } + signatories: + type: array + items: + type: object + properties: { role: { type: string }, name: { type: string } } + required: [] + additionalProperties: false + change_record: + type: array + items: + type: object + properties: { issue: { type: string }, date: { type: string }, changes: { type: string }, author: { type: string } } + required: [] + additionalProperties: false + documents: + type: array + items: + type: object + properties: { ref: { type: string }, id: { type: string }, title: { type: string }, issue: { type: string } } + required: [] + additionalProperties: false + required: [] + additionalProperties: false + sections: + type: array + items: + type: object + properties: { key: { type: string }, enabled: { type: boolean }, options: { type: object, additionalProperties: {} } } + required: [ key ] + additionalProperties: false + required: [ report_type ] + additionalProperties: false + ReportOptionSpec: + oneOf: + - type: object + properties: + key: { type: string } + label: { type: string } + default: {} + kind: { type: string, const: select } + choices: + type: array + items: + type: object + properties: { value: { type: string }, label: { type: string } } + required: [ value, label ] + additionalProperties: true + required: [ key, label, default, kind, choices ] + additionalProperties: true + - type: object + properties: + key: { type: string } + label: { type: string } + default: {} + kind: { type: string, const: multi_select } + choices: + type: array + items: + type: object + properties: { value: { type: string }, label: { type: string } } + required: [ value, label ] + additionalProperties: true + required: [ key, label, default, kind, choices ] + additionalProperties: true + - type: object + properties: + key: { type: string } + label: { type: string } + default: {} + kind: { type: string, const: text } + multiline: { type: boolean } + required: [ key, label, default, kind, multiline ] + additionalProperties: true + ReportSectionSpec: + type: object + properties: + key: { type: string } + title: { type: string } + description: { type: string } + report_types: { type: array, items: { type: string, enum: [ vcd, coverage ] } } + default_enabled: { type: boolean } + options: { type: array, items: { $ref: "#/components/schemas/ReportOptionSpec" } } + required: [ key, title, description, report_types, default_enabled, options ] + additionalProperties: true + ReportTemplateView: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + owner_id: { type: integer } + name: { type: string } + report_type: + type: string + enum: + - vcd + - coverage + visibility: + type: string + enum: + - private + - shared + owner_name: { type: string } + created_at: { type: string } + updated_at: { type: string } + can_edit: { type: boolean } + definition: {} + required: + - id + - project_id + - owner_id + - name + - report_type + - visibility + - owner_name + - created_at + - updated_at + - can_edit + - definition + additionalProperties: true + ReportTemplateInput: + type: object + properties: + name: { anyOf: [ { type: string }, { type: "null" } ] } + visibility: { anyOf: [ { type: string }, { type: "null" } ] } + definition: { anyOf: [ { $ref: "#/components/schemas/ReportDefinitionInput" }, { type: "null" } ] } + required: [] + additionalProperties: false + ExcelImportResult: + type: object + properties: + success: { type: boolean } + message: { type: string } + imported_count: { type: integer } + errors: { type: array, items: { type: string } } + imported_requirement_ids: { type: array, items: { type: integer } } + required: [ success, message, imported_count, errors, imported_requirement_ids ] + additionalProperties: true + CustomFieldValueDisplay: + type: object + properties: + field_id: { type: integer } + label: { type: string } + value: { anyOf: [ { type: string }, { type: "null" } ] } + required: [ field_id, label, value ] + additionalProperties: true + Requirement: + type: object + properties: + id: { type: integer } + status_id: { type: integer } + author_id: { type: integer } + reviewer_id: { type: integer } + category_id: { type: integer } + applicability_id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + reference_code: { type: string } + creation_date: { type: string } + update_date: { type: string } + current_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } + parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } + approved_by: { anyOf: [ { type: integer }, { type: "null" } ] } + deadline_date: { anyOf: [ { type: string }, { type: "null" } ] } + justification: { anyOf: [ { type: string }, { type: "null" } ] } + approved_at: { anyOf: [ { type: string }, { type: "null" } ] } + approval_state: { type: string, enum: [ draft, reviewed, approved ] } + same_as_current: { type: boolean } + custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueDisplay" } } + required: + - id + - status_id + - author_id + - reviewer_id + - category_id + - applicability_id + - project_id + - title + - description + - reference_code + - creation_date + - update_date + - current_version_id + - parent_id + - approved_by + - deadline_date + - justification + - approved_at + - approval_state + additionalProperties: true + RequirementVersion: + type: object + properties: + id: { type: integer } + requirement_id: { type: integer } + status_id: { type: integer } + author_id: { type: integer } + reviewer_id: { type: integer } + category_id: { type: integer } + applicability_id: { type: integer } + title: { type: string } + description: { type: string } + created_at: { type: string } + approved_by: { anyOf: [ { type: integer }, { type: "null" } ] } + reviewed_by: { anyOf: [ { type: integer }, { type: "null" } ] } + justification: { anyOf: [ { type: string }, { type: "null" } ] } + deadline_date: { anyOf: [ { type: string }, { type: "null" } ] } + approved_at: { anyOf: [ { type: string }, { type: "null" } ] } + reviewed_at: { anyOf: [ { type: string }, { type: "null" } ] } + approval_state: { type: string, enum: [ draft, reviewed, approved ] } + required: + - id + - requirement_id + - status_id + - author_id + - reviewer_id + - category_id + - applicability_id + - title + - description + - created_at + - approved_by + - reviewed_by + - justification + - deadline_date + - approved_at + - reviewed_at + - approval_state + additionalProperties: true + RequirementListRow: + allOf: + - { $ref: "#/components/schemas/Requirement" } + - type: object + properties: + verification_method_ids: { type: array, items: { type: integer } } + parent_requirement_ids: { type: array, items: { type: integer } } + required: [ verification_method_ids, parent_requirement_ids ] + additionalProperties: true + RequirementWithTraceSummary: + allOf: + - { $ref: "#/components/schemas/Requirement" } + - type: object + properties: + trace_summary: + type: object + properties: + parent_links: { type: array, items: { $ref: "#/components/schemas/RequirementVersionLink" } } + child_ids: { type: array, items: { type: integer } } + linked_test_ids: { type: array, items: { type: integer } } + required: [ parent_links, child_ids, linked_test_ids ] + additionalProperties: true + required: [ trace_summary ] + additionalProperties: true + RequirementVersionDetail: + allOf: + - { $ref: "#/components/schemas/RequirementVersion" } + - type: object + properties: + custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueDisplay" } } + verification_method_ids: { type: array, items: { type: integer } } + required: [ custom_fields, verification_method_ids ] + additionalProperties: true + CustomFieldValueInput: + type: object + properties: { field_id: { type: integer }, value: { anyOf: [ { type: string }, { type: "null" } ] } } + required: [ field_id ] + additionalProperties: true + RequirementCreateRequest: + type: object + properties: + title: { type: string } + description: { type: string } + reference_code: { type: string } + author_id: { type: integer } + category_id: { type: integer } + status_id: { type: integer } + reviewer_id: { type: integer } + applicability_id: { type: integer } + project_id: { type: integer } + justification: { anyOf: [ { type: string }, { type: "null" } ] } + verification_method_ids: { type: array, items: { type: integer }, minItems: 1 } + custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueInput" } } + parent_links: + type: array + items: + type: object + properties: + target_version_id: { type: integer } + link_type: { type: string } + rationale: { anyOf: [ { type: string }, { type: "null" } ] } + required: [ target_version_id, link_type ] + additionalProperties: true + required: + - title + - description + - reference_code + - author_id + - category_id + - status_id + - reviewer_id + - applicability_id + - project_id + - verification_method_ids + additionalProperties: true + RequirementPatch: + type: object + properties: + title: { anyOf: [ { type: string }, { type: "null" } ] } + description: { anyOf: [ { type: string }, { type: "null" } ] } + justification: { anyOf: [ { type: string }, { type: "null" } ] } + status_id: { anyOf: [ { type: integer }, { type: "null" } ] } + author_id: { anyOf: [ { type: integer }, { type: "null" } ] } + reviewer_id: { anyOf: [ { type: integer }, { type: "null" } ] } + category_id: { anyOf: [ { type: integer }, { type: "null" } ] } + applicability_id: { anyOf: [ { type: integer }, { type: "null" } ] } + verification_method_ids: { anyOf: [ { type: array, items: { type: integer } }, { type: "null" } ] } + custom_fields: + { + anyOf: [ { type: array, items: { $ref: "#/components/schemas/CustomFieldValueInput" } }, { type: "null" } ] + } + required: [] + additionalProperties: true + Verification: + type: object + properties: + id: { type: integer } + status_id: { type: integer } + project_id: { type: integer } + author_id: { type: integer } + reviewer_id: { type: integer } + name: { type: string } + reference_code: { type: string } + description: { type: string } + source: { type: string } + parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } + verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } + status_set_by: { anyOf: [ { type: integer }, { type: "null" } ] } + status_set_at: { anyOf: [ { type: string }, { type: "null" } ] } + required: + - id + - status_id + - project_id + - author_id + - reviewer_id + - name + - reference_code + - description + - source + - parent_id + - verification_method_id + - status_set_by + - status_set_at + additionalProperties: true + NewVerification: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + status_id: { type: integer } + project_id: { type: integer } + author_id: { type: integer } + reviewer_id: { type: integer } + name: { type: string } + reference_code: { type: string } + description: { type: string } + source: { type: string } + parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } + verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ status_id, project_id, author_id, reviewer_id, name, reference_code, description, source ] + additionalProperties: true + Baseline: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + created_by: { type: integer } + name: { type: string } + created_at: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + source_saved_view_id: { anyOf: [ { type: integer }, { type: "null" } ] } + source_view_definition: { anyOf: [ {}, { type: "null" } ] } + required: + - id + - project_id + - created_by + - name + - created_at + - description + - source_saved_view_id + - source_view_definition + additionalProperties: true + BaselineTraceability: + type: object + properties: + baseline_id: { type: integer } + requirement_id: { type: integer } + verification_id: { type: integer } + suspect: { type: boolean } + suspect_at: { anyOf: [ { type: string }, { type: "null" } ] } + suspect_reason: { anyOf: [ { type: string }, { type: "null" } ] } + required: [ baseline_id, requirement_id, verification_id, suspect, suspect_at, suspect_reason ] + additionalProperties: true + BaselineVerification: + type: object + properties: + status_id: { type: integer } + project_id: { type: integer } + author_id: { type: integer } + reviewer_id: { type: integer } + name: { type: string } + reference_code: { type: string } + description: { type: string } + source: { type: string } + parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } + verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } + baseline_id: { type: integer } + verification_id: { type: integer } + required: + - status_id + - project_id + - author_id + - reviewer_id + - name + - reference_code + - description + - source + - parent_id + - verification_method_id + - baseline_id + - verification_id + additionalProperties: true + BaselineDiff: + type: object + properties: + requirements_only_in_a: { type: array, items: { type: integer } } + requirements_only_in_b: { type: array, items: { type: integer } } + trace_only_in_a: { type: array, items: { type: array, items: { type: integer }, minItems: 2, maxItems: 2 } } + trace_only_in_b: { type: array, items: { type: array, items: { type: integer }, minItems: 2, maxItems: 2 } } + use_cases_only_in_a: { type: array, items: { type: integer } } + use_cases_only_in_b: { type: array, items: { type: integer } } + use_cases_changed: + type: array + items: + type: object + properties: { use_case_id: { type: integer }, version_a: { type: integer }, version_b: { type: integer } } + required: [ use_case_id, version_a, version_b ] + additionalProperties: true + required: + - requirements_only_in_a + - requirements_only_in_b + - trace_only_in_a + - trace_only_in_b + - use_cases_only_in_a + - use_cases_only_in_b + - use_cases_changed + additionalProperties: true + TextDiffResult: + type: object + properties: + added: { type: array, items: { type: string } } + removed: { type: array, items: { type: string } } + unchanged: { type: array, items: { type: string } } + required: [ added, removed, unchanged ] + additionalProperties: true + SingleValueDiff: + type: object + properties: + old_id: { type: integer } + new_id: { type: integer } + unchanged: { type: integer } + unchanged_label: { type: string } + old_label: { type: string } + new_label: { type: string } + required: [] + additionalProperties: true + VerificationDiff: + type: object + properties: + added_ids: { type: array, items: { type: integer } } + removed_ids: { type: array, items: { type: integer } } + unchanged_ids: { type: array, items: { type: integer } } + added_labels: { type: array, items: { type: string } } + removed_labels: { type: array, items: { type: string } } + unchanged_labels: { type: array, items: { type: string } } + required: [ added_ids, removed_ids, unchanged_ids ] + additionalProperties: true + RequirementDiff: + type: object + properties: + text: + type: object + properties: + title: { $ref: "#/components/schemas/TextDiffResult" } + description: { $ref: "#/components/schemas/TextDiffResult" } + justification: { $ref: "#/components/schemas/TextDiffResult" } + required: [ title, description, justification ] + additionalProperties: true + metadata: + type: object + properties: + status: { $ref: "#/components/schemas/SingleValueDiff" } + category: { $ref: "#/components/schemas/SingleValueDiff" } + applicability: { $ref: "#/components/schemas/SingleValueDiff" } + verification: { $ref: "#/components/schemas/VerificationDiff" } + custom_fields: + type: array + items: + type: object + properties: + field_id: { type: integer } + label: { type: string } + old_value: { anyOf: [ { type: string }, { type: "null" } ] } + new_value: { anyOf: [ { type: string }, { type: "null" } ] } + unchanged: { type: boolean } + required: [ field_id, label, old_value, new_value, unchanged ] + additionalProperties: true + required: [ status, category, applicability, verification, custom_fields ] + additionalProperties: true + required: [ text, metadata ] + additionalProperties: true + VerificationVersionDiff: + type: object + properties: + text: + type: object + properties: + name: { $ref: "#/components/schemas/TextDiffResult" } + description: { $ref: "#/components/schemas/TextDiffResult" } + source: { $ref: "#/components/schemas/TextDiffResult" } + reference_code: { $ref: "#/components/schemas/TextDiffResult" } + required: [ name, description, source, reference_code ] + additionalProperties: true + metadata: + type: object + properties: + status: { $ref: "#/components/schemas/SingleValueDiff" } + verification_method: { $ref: "#/components/schemas/SingleValueDiff" } + parent: { $ref: "#/components/schemas/SingleValueDiff" } + required: [ status, verification_method, parent ] + additionalProperties: true + required: [ text, metadata ] + additionalProperties: true + Dsm: + type: object + properties: + order: { type: string } + link_types: { type: array, items: { type: string } } + requirements: + type: array + items: + type: object + properties: + id: { type: integer } + index: { type: integer } + category_id: { type: integer } + depth: { type: integer } + reference_code: { type: string } + title: { type: string } + approval_state: { type: string } + category: { type: string } + parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ id, index, category_id, depth, reference_code, title, approval_state, category, parent_id ] + additionalProperties: true + cells: + type: array + items: + type: object + properties: + row: { type: integer } + col: { type: integer } + link_types: { type: array, items: { type: string } } + link_ids: { type: array, items: { type: integer } } + upstream_changed: { type: boolean } + in_loop: { type: boolean } + required: [ row, col, link_types, link_ids, upstream_changed, in_loop ] + additionalProperties: true + groups: + type: array + items: + type: object + properties: { label: { type: string }, start: { type: integer }, end: { type: integer } } + required: [ label, start, end ] + additionalProperties: true + loops: + type: array + items: + type: object + properties: + requirement_ids: { type: array, items: { type: integer } } + path: { type: array, items: { type: integer } } + required: [ requirement_ids, path ] + additionalProperties: true + stats: + type: object + properties: + requirements: { type: integer } + links: { type: integer } + cells: { type: integer } + loops: { type: integer } + upstream_changed: { type: integer } + external_links: { type: integer } + required: [ requirements, links, cells, loops, upstream_changed, external_links ] + additionalProperties: true + required: [ order, link_types, requirements, cells, groups, loops, stats ] + additionalProperties: true + EntityActivityItem: + type: object + properties: + log_id: { type: integer } + user_id: { type: integer } + username: { type: string } + action_type: { type: string } + summary: { type: string } + created_at: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + changes: { type: array, items: { $ref: "#/components/schemas/LogChangeDetail" } } + required: [ log_id, user_id, username, action_type, summary, created_at, description, changes ] + additionalProperties: true + MatrixLink: + type: object + properties: + req_id: { type: integer } + verification_id: { type: integer } + project_id: { type: integer } + creation_date: { type: string } + suspect: { type: boolean } + suspect_at: { anyOf: [ { type: string }, { type: "null" } ] } + suspect_reason: { anyOf: [ { type: string }, { type: "null" } ] } + cleared_at: { anyOf: [ { type: string }, { type: "null" } ] } + cleared_by: { anyOf: [ { type: integer }, { type: "null" } ] } + triggering_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } + triggering_user_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: + - req_id + - verification_id + - project_id + - creation_date + - suspect + - suspect_at + - suspect_reason + - cleared_at + - cleared_by + - triggering_version_id + - triggering_user_id + additionalProperties: true + VerificationControlView: + type: object + properties: + verification_id: { type: integer } + verification_level: { anyOf: [ { type: string }, { type: "null" } ] } + verification_stage: { anyOf: [ { type: string }, { type: "null" } ] } + evidence_reference: { anyOf: [ { type: string }, { type: "null" } ] } + updated_by: { anyOf: [ { type: integer }, { type: "null" } ] } + updated_at: { anyOf: [ { type: string }, { type: "null" } ] } + suggestions: + type: object + properties: { levels: { type: array, items: { type: string } }, stages: { type: array, items: { type: string } } } + required: [ levels, stages ] + additionalProperties: true + required: + - verification_id + - verification_level + - verification_stage + - evidence_reference + - updated_by + - updated_at + - suggestions + additionalProperties: true + RequirementCloseOutView: + type: object + properties: + requirement_id: { type: integer } + compliance: + anyOf: + - type: string + enum: + - C + - PC + - NC + - type: "null" + note: { anyOf: [ { type: string }, { type: "null" } ] } + set_by: { anyOf: [ { type: integer }, { type: "null" } ] } + set_at: { anyOf: [ { type: string }, { type: "null" } ] } + close_out: + type: object + properties: { status: { type: string, enum: [ open, closed ] }, reason: { type: string } } + required: [ status, reason ] + additionalProperties: true + verifications: + type: array + items: + type: object + properties: + id: { type: integer } + reference_code: { type: string } + name: { type: string } + status_id: { type: integer } + status_title: { type: string } + outcome: { type: string, enum: [ passed, failed, in_progress, not_run ] } + required: [ id, reference_code, name, status_id, status_title, outcome ] + additionalProperties: true + required: [ requirement_id, compliance, note, set_by, set_at, close_out, verifications ] + additionalProperties: true + VerificationControlInput: + type: object + properties: + verification_level: { anyOf: [ { type: string }, { type: "null" } ] } + verification_stage: { anyOf: [ { type: string }, { type: "null" } ] } + evidence_reference: { anyOf: [ { type: string }, { type: "null" } ] } + required: [] + additionalProperties: false + RequirementVersionLink: + type: object + properties: + id: { type: integer } + source_version_id: { type: integer } + target_version_id: { type: integer } + project_id: { type: integer } + link_type: { type: string } + created_at: { type: string } + rationale: { anyOf: [ { type: string }, { type: "null" } ] } + metadata: { anyOf: [ {}, { type: "null" } ] } + required: [ id, source_version_id, target_version_id, project_id, link_type, created_at, rationale, metadata ] + additionalProperties: true + CommentResponse: + type: object + properties: + id: { type: integer } + requirement_id: { type: integer } + author_id: { type: integer } + requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } + author_name: { type: string } + body: { type: string } + created_at: { type: string } + required: [ id, requirement_id, author_id, requirement_version_id, author_name, body, created_at ] + additionalProperties: true + McpAuditRequest: + type: object + properties: + project_id: { anyOf: [ { type: integer }, { type: "null" } ] } + session_id: { anyOf: [ { type: string }, { type: "null" } ] } + tool_name: + type: string + enum: + - list_projects + - get_requirement + - list_requirements + - get_versions + - semantic_search_requirements + - compare_versions + - trace_up + - trace_down + - coverage_report + - dependency_matrix + - get_baseline + - diff_baselines + - list_verifications + - get_verification + - list_baselines + - get_requirement_activity + - get_verification_activity + - list_requirement_comments + - get_verification_matrix + - list_project_catalog + - diff_baseline_vs_current + - create_requirement + - patch_requirement + - set_approval + - create_baseline + - create_requirement_comment + - create_verification + - update_verification + - put_verification_matrix + - clear_suspect + params_summary: { anyOf: [ { type: string }, { type: "null" } ] } + result_summary: { anyOf: [ { type: string }, { type: "null" } ] } + is_write: { type: boolean } + required: [ tool_name, is_write ] + additionalProperties: true + ProjectStatus: { type: string, enum: [ Active, Completed, OnHold, Cancelled ] } + Project: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + creation_date: { anyOf: [ { type: string }, { type: "null" } ] } + update_date: { anyOf: [ { type: string }, { type: "null" } ] } + archived_at: { anyOf: [ { type: string }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + archived_by: { anyOf: [ { type: integer }, { type: "null" } ] } + status: { $ref: "#/components/schemas/ProjectStatus" } + required: + - id + - name + - slug + - description + - creation_date + - update_date + - archived_at + - owner_id + - group_id + - archived_by + - status + additionalProperties: true + GroupResponse: + type: object + properties: + id: { type: integer } + name: { type: string } + slug: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + created_at: { type: string } + updated_at: { type: string } + required: [ id, name, slug, description, owner_id, created_at, updated_at ] + additionalProperties: true + GroupMemberResponse: + type: object + properties: { user_id: { type: integer }, role: { type: integer }, role_label: { type: string } } + required: [ user_id, role, role_label ] + additionalProperties: true + Notification: + type: object + properties: + id: { type: integer } + user_id: { type: integer } + project_id: { anyOf: [ { type: integer }, { type: "null" } ] } + entity_id: { anyOf: [ { type: integer }, { type: "null" } ] } + actor_id: { anyOf: [ { type: integer }, { type: "null" } ] } + notification_type: { type: string } + title: { type: string } + created_at: { type: string } + body: { anyOf: [ { type: string }, { type: "null" } ] } + entity_type: { anyOf: [ { type: string }, { type: "null" } ] } + read: { type: boolean } + emailed: { type: boolean } + required: + - id + - user_id + - project_id + - entity_id + - actor_id + - notification_type + - title + - created_at + - body + - entity_type + - read + - emailed + additionalProperties: true + NotificationPreference: + type: object + properties: + id: { type: integer } + user_id: { type: integer } + project_id: { type: integer } + notify_in_app: { type: boolean } + notify_email: { type: boolean } + required: [ id, user_id, project_id, notify_in_app, notify_email ] + additionalProperties: true + MemberResponse: + type: object + properties: + user_id: { type: integer } + role: { type: integer } + role_label: { type: string } + username: { type: string } + name: { type: string } + required: [ user_id, role, role_label, username, name ] + additionalProperties: true + ProjectReviewersResponse: + type: object + properties: { user_ids: { type: array, items: { type: integer } } } + required: [ user_ids ] + additionalProperties: true + EffectivePermissions: + type: object + properties: + view_requirements: { type: boolean } + edit_requirements: { type: boolean } + approve_versions: { type: boolean } + is_project_reviewer: { type: boolean } + manage_custom_fields: { type: boolean } + manage_project_configuration: { type: boolean } + manage_project_members: { type: boolean } + archived: { type: boolean } + required: + - view_requirements + - edit_requirements + - approve_versions + - is_project_reviewer + - manage_custom_fields + - manage_project_configuration + - manage_project_members + - archived + additionalProperties: true + CustomFieldDefinition: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + label: { type: string } + field_type: + type: string + enum: + - text + - enum + - boolean + - number + enum_values: { anyOf: [ {}, { type: "null" } ] } + sort_order: { type: integer } + created_at: { type: string } + required: [ id, project_id, label, field_type, enum_values, sort_order, created_at ] + additionalProperties: true + CustomFieldDefinitionPayload: + type: object + properties: + label: { type: string } + field_type: + type: string + enum: + - text + - enum + - boolean + - number + enum_values: { anyOf: [ { type: array, items: { type: string } }, { type: "null" } ] } + sort_order: { anyOf: [ { type: integer }, { type: "null" } ] } + required: [ label, field_type ] + additionalProperties: true + SavedView: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + owner_id: { type: integer } + name: { type: string } + visibility: + type: string + enum: + - private + - shared + created_at: { type: string } + updated_at: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + locked_at: { anyOf: [ { type: string }, { type: "null" } ] } + locked: { type: boolean } + definition: {} + required: + - id + - project_id + - owner_id + - name + - visibility + - created_at + - updated_at + - description + - locked_at + - locked + - definition + additionalProperties: true + SavedViewPayload: + type: object + properties: + name: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + visibility: + type: string + enum: + - private + - shared + definition: {} + required: [ name, visibility, definition ] + additionalProperties: true + DashboardProject: + type: object + properties: + project_id: { type: integer } + requirements_count: { type: integer } + tests_count: { type: integer } + project_slug: { type: string } + project_base_path: { type: string } + name: { type: string } + status_id: { type: string } + project_status_normalized: { type: string } + project_status_badge: { type: string } + project_initial: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + creation_date: { anyOf: [ { type: string }, { type: "null" } ] } + update_date: { anyOf: [ { type: string }, { type: "null" } ] } + archived_at: { anyOf: [ { type: string }, { type: "null" } ] } + project_owner_name: { anyOf: [ { type: string }, { type: "null" } ] } + group_name: { anyOf: [ { type: string }, { type: "null" } ] } + group_slug: { anyOf: [ { type: string }, { type: "null" } ] } + group_path: { anyOf: [ { type: string }, { type: "null" } ] } + role_label: { anyOf: [ { type: string }, { type: "null" } ] } + owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } + group_id: { anyOf: [ { type: integer }, { type: "null" } ] } + role_id: { anyOf: [ { type: integer }, { type: "null" } ] } + archived: { type: boolean } + can_view_group: { type: boolean } + required: + - project_id + - requirements_count + - tests_count + - project_slug + - project_base_path + - name + - status_id + - project_status_normalized + - project_status_badge + - project_initial + - description + - creation_date + - update_date + - archived_at + - project_owner_name + - group_name + - group_slug + - group_path + - role_label + - owner_id + - group_id + - role_id + - archived + - can_view_group + additionalProperties: true + AttentionItem: + type: object + properties: + project_id: { type: integer } + title: { type: string } + requirement_id: { anyOf: [ { type: integer }, { type: "null" } ] } + use_case_id: { anyOf: [ { type: integer }, { type: "null" } ] } + count: { anyOf: [ { type: integer }, { type: "null" } ] } + notification_id: { anyOf: [ { type: integer }, { type: "null" } ] } + reference_code: { anyOf: [ { type: string }, { type: "null" } ] } + notification_type: { anyOf: [ { type: string }, { type: "null" } ] } + at: { anyOf: [ { type: string }, { type: "null" } ] } + kind: { type: string, enum: [ approval, review, suspect, notification ] } + required: + - project_id + - title + - requirement_id + - use_case_id + - count + - notification_id + - reference_code + - notification_type + - at + - kind + additionalProperties: true + Applicability: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ id, project_id, title, description, tag ] + additionalProperties: true + NewApplicability: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ title, description, tag, project_id ] + additionalProperties: true + Category: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ id, project_id, title, description, tag ] + additionalProperties: true + NewCategory: type: object properties: - id: - type: integer - entity_type: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ title, description, tag, project_id ] + additionalProperties: true + VerificationMethod: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ id, project_id, title, description, tag ] + additionalProperties: true + NewVerificationMethod: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + required: [ title, description, tag, project_id ] + additionalProperties: true + RequirementStatus: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + is_system: { type: boolean } + tag_color: { anyOf: [ { type: string }, { type: "null" } ] } + required: [ id, project_id, title, description, tag, is_system, tag_color ] + additionalProperties: true + NewRequirementStatus: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + is_system: { type: boolean } + tag_color: { anyOf: [ { type: string }, { type: "null" } ] } + required: [ title, description, tag, project_id ] + additionalProperties: true + VerificationStatus: + type: object + properties: + id: { type: integer } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + is_system: { type: boolean } + tag_color: { anyOf: [ { type: string }, { type: "null" } ] } + outcome: type: string enum: - - requirement - - verification - entity_id: - type: integer - filename: - type: string - content_type: - type: string - size_bytes: - type: integer - uploaded_by: - type: - - integer - - "null" - uploaded_by_name: - type: - - string - - "null" - created_at: - type: string - deleted: - type: boolean + - passed + - failed + - in_progress + - not_run + required: [ id, project_id, title, description, tag, is_system, tag_color, outcome ] + additionalProperties: true + NewVerificationStatus: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { type: integer } + title: { type: string } + description: { type: string } + tag: { type: string } + is_system: { type: boolean } + tag_color: { anyOf: [ { type: string }, { type: "null" } ] } + outcome: { anyOf: [ { type: string, enum: [ passed, failed, in_progress, not_run ] }, { type: "null" } ] } + required: [ title, description, tag, project_id ] + additionalProperties: true + UserCreateRequest: + type: object + properties: + username: { type: string } + name: { type: string } + email: { type: string } + password: { type: string } + is_admin: { type: boolean } + required: [ username, name, email, password, is_admin ] + additionalProperties: true + UpdateUser: + type: object + properties: + id: { anyOf: [ { type: integer }, { type: "null" } ] } + username: { type: string } + name: { type: string } + email: { type: string } + is_admin: { type: boolean } + required: [ username, name, email, is_admin ] + additionalProperties: true + BackupInfo: + type: object + properties: + attachments_available: { type: boolean } + attachment_files: { type: integer, minimum: 0 } + attachment_bytes: { type: integer, minimum: 0 } + required: [ attachments_available, attachment_files, attachment_bytes ] + additionalProperties: true + LogChangeDetail: + type: object + properties: { field: { type: string }, old_value: { type: string }, new_value: { type: string } } + required: [ field, old_value, new_value ] + additionalProperties: true + AdminLogItem: + type: object + properties: + log_id: { type: integer } + user_id: { type: integer } + username: { type: string } + action_type: { type: string } + summary: { type: string } + description: { anyOf: [ { type: string }, { type: "null" } ] } + created_at: { type: string } + changes: { type: array, items: { $ref: "#/components/schemas/LogChangeDetail" } } + entity_type: { type: string } + entity_id: { anyOf: [ { type: integer }, { type: "null" } ] } + project_id: { anyOf: [ { type: integer }, { type: "null" } ] } + required: + - log_id + - user_id + - username + - action_type + - summary + - description + - created_at + - changes + - entity_type + - entity_id + - project_id + additionalProperties: true + AdminLogList: + type: object + properties: + items: { type: array, items: { $ref: "#/components/schemas/AdminLogItem" } } + total: { type: integer } + limit: { type: integer } + offset: { type: integer } + required: [ items, total, limit, offset ] + additionalProperties: true + LogStatsResponse: + type: object + properties: + since: { type: string } + until: { type: string } + total: { type: integer } + active_users: { type: integer } + by_day: + type: array + items: + type: object + properties: { day: { type: string, format: date }, count: { type: integer } } + required: [ day, count ] + additionalProperties: true + by_action: + type: array + items: + type: object + properties: { action_type: { type: string }, count: { type: integer } } + required: [ action_type, count ] + additionalProperties: true + by_user: + type: array + items: + type: object + properties: { user_id: { type: integer }, username: { type: string }, count: { type: integer } } + required: [ user_id, username, count ] + additionalProperties: true + required: [ since, until, total, active_users, by_day, by_action, by_user ] + additionalProperties: true + AttachmentView: + type: object + properties: + id: { type: integer } + entity_type: { type: string, enum: [ requirement, verification ] } + entity_id: { type: integer } + filename: { type: string } + content_type: { type: string } + size_bytes: { type: integer } + uploaded_by: { type: [ integer, "null" ] } + uploaded_by_name: { type: [ string, "null" ] } + created_at: { type: string } + deleted: { type: boolean } required: - id - entity_type @@ -4635,22 +8410,13 @@ components: AttachmentStorage: type: object properties: - used_bytes: - type: integer - quota_bytes: - type: integer - quota_is_default: - type: boolean - default_quota_bytes: - type: integer - retained_by_baselines_bytes: - type: integer - max_file_bytes: - type: integer - allowed_extensions: - type: array - items: - type: string + used_bytes: { type: integer } + quota_bytes: { type: integer } + quota_is_default: { type: boolean } + default_quota_bytes: { type: integer } + retained_by_baselines_bytes: { type: integer } + max_file_bytes: { type: integer } + allowed_extensions: { type: array, items: { type: string } } required: - used_bytes - quota_bytes @@ -4662,109 +8428,51 @@ components: additionalProperties: true AuthProfileUpdate: type: object - properties: - name: - type: string - email: - type: string - current_password: - type: - - string - - "null" - required: - - name - - email + properties: { name: { type: string }, email: { type: string }, current_password: { type: [ string, "null" ] } } + required: [ name, email ] additionalProperties: true AuthIdentities: type: object properties: - password_configured: - type: boolean + password_configured: { type: boolean } identities: type: array items: type: object properties: - id: - type: integer - provider: - type: string - created_at: - type: string - last_login_at: - type: - - string - - "null" - required: - - id - - provider - - created_at - - last_login_at + id: { type: integer } + provider: { type: string } + created_at: { type: string } + last_login_at: { type: [ string, "null" ] } + required: [ id, provider, created_at, last_login_at ] additionalProperties: true - required: - - password_configured - - identities + required: [ password_configured, identities ] additionalProperties: true AuthChangePassword: type: object - properties: - current_password: - type: string - new_password: - type: string - confirm_password: - type: string - required: - - current_password - - new_password - - confirm_password + properties: { current_password: { type: string }, new_password: { type: string }, confirm_password: { type: string } } + required: [ current_password, new_password, confirm_password ] additionalProperties: true AuthStatus: type: object - properties: - status: - type: string - const: ok - required: - - status + properties: { status: { type: string, const: ok } } + required: [ status ] additionalProperties: true CacheStats: type: object properties: - total_entries: - type: integer - minimum: 0 - active_entries: - type: integer - minimum: 0 - expired_entries: - type: integer - minimum: 0 - hits: - type: integer - minimum: 0 - misses: - type: integer - minimum: 0 - total_requests: - type: integer - minimum: 0 - average_access_time_ns: - type: integer - minimum: 0 - total_access_time_ns: - type: integer - minimum: 0 - cache_size_bytes: - type: integer - minimum: 0 - hit_rate: - type: number - miss_rate: - type: number - last_cleanup: - type: string - format: date-time + total_entries: { type: integer, minimum: 0 } + active_entries: { type: integer, minimum: 0 } + expired_entries: { type: integer, minimum: 0 } + hits: { type: integer, minimum: 0 } + misses: { type: integer, minimum: 0 } + total_requests: { type: integer, minimum: 0 } + average_access_time_ns: { type: integer, minimum: 0 } + total_access_time_ns: { type: integer, minimum: 0 } + cache_size_bytes: { type: integer, minimum: 0 } + hit_rate: { type: number } + miss_rate: { type: number } + last_cleanup: { type: string, format: date-time } required: - total_entries - active_entries @@ -4782,861 +8490,609 @@ components: CacheRecommendations: type: object properties: - recommendations: - type: array - items: - type: string - priority: - type: string - enum: - - high - - medium - - low - analysis_timestamp: - type: string - format: date-time - required: - - recommendations - - priority - - analysis_timestamp + recommendations: { type: array, items: { type: string } } + priority: { type: string, enum: [ high, medium, low ] } + analysis_timestamp: { type: string, format: date-time } + required: [ recommendations, priority, analysis_timestamp ] additionalProperties: true CacheActionResult: type: object - properties: - message: - type: string - timestamp: - type: string - format: date-time - required: - - message - - timestamp + properties: { message: { type: string }, timestamp: { type: string, format: date-time } } + required: [ message, timestamp ] additionalProperties: true CacheCleanupResult: allOf: - - $ref: "#/components/schemas/CacheActionResult" + - { $ref: "#/components/schemas/CacheActionResult" } - type: object - properties: - cleaned_entries: - type: integer - minimum: 0 - required: - - cleaned_entries + properties: { cleaned_entries: { type: integer, minimum: 0 } } + required: [ cleaned_entries ] additionalProperties: true external_ProviderDiscovery_response: type: object properties: - password_enabled: {type: boolean} - external: - type: array - items: {$ref: '#/components/schemas/external_PublicAuthProvider_response'} + password_enabled: { type: boolean } + external: { type: array, items: { $ref: '#/components/schemas/external_PublicAuthProvider_response' } } additionalProperties: true - required: [password_enabled, external] + required: [ password_enabled, external ] external_PublicAuthProvider_response: type: object - properties: - id: {type: string} - display_name: {type: string} + properties: { id: { type: string }, display_name: { type: string } } additionalProperties: true - required: [id, display_name] + required: [ id, display_name ] entities_User_response: type: object properties: - id: {type: integer} - username: {type: string} - name: {type: string} - email: {type: string} - creation_date: {type: string} - last_login: {type: string} - is_admin: {type: boolean} - email_verified: {type: boolean} + id: { type: integer } + username: { type: string } + name: { type: string } + email: { type: string } + creation_date: { type: string } + last_login: { type: string } + is_admin: { type: boolean } + email_verified: { type: boolean } additionalProperties: true - required: [id, username, name, email, creation_date, last_login, is_admin, email_verified] + required: [ id, username, name, email, creation_date, last_login, is_admin, email_verified ] use_cases_UseCaseRow_response: type: object properties: - id: {type: integer} - reference_code: {type: string} - title: {type: string} - primary_actor: - anyOf: - - {type: string} - - {type: 'null'} - step_count: {type: integer} - alternative_count: {type: integer} - exception_count: {type: integer} - approval_state: - anyOf: - - {type: string} - - {type: 'null'} - current_version_number: - anyOf: - - {type: integer} - - {type: 'null'} - has_draft: {type: boolean} - link_count: {type: integer} - suspect_count: {type: integer} - updated_at: {type: string} + id: { type: integer } + reference_code: { type: string } + title: { type: string } + primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } + step_count: { type: integer } + alternative_count: { type: integer } + exception_count: { type: integer } + approval_state: { anyOf: [ { type: string }, { type: 'null' } ] } + current_version_number: { anyOf: [ { type: integer }, { type: 'null' } ] } + has_draft: { type: boolean } + link_count: { type: integer } + suspect_count: { type: integer } + updated_at: { type: string } additionalProperties: true - required: [id, reference_code, title, primary_actor, step_count, alternative_count, exception_count, approval_state, - current_version_number, has_draft, link_count, suspect_count, updated_at] + required: + [ + id, + reference_code, + title, + primary_actor, + step_count, + alternative_count, + exception_count, + approval_state, + current_version_number, + has_draft, + link_count, + suspect_count, + updated_at + ] use_cases_NextCode_response: type: object - properties: - reference_code: {type: string} + properties: { reference_code: { type: string } } additionalProperties: true - required: [reference_code] + required: [ reference_code ] use_cases_UseCaseDetail_response: type: object properties: - id: {type: integer} - project_id: {type: integer} - reference_code: {type: string} - title: {type: string} - created_by: - anyOf: - - {type: integer} - - {type: 'null'} - created_at: {type: string} - updated_at: {type: string} - current_version: - anyOf: - - {$ref: '#/components/schemas/use_cases_VersionView_response'} - - {type: 'null'} - draft: - anyOf: - - {$ref: '#/components/schemas/use_cases_DraftSummary_response'} - - {type: 'null'} + id: { type: integer } + project_id: { type: integer } + reference_code: { type: string } + title: { type: string } + created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + created_at: { type: string } + updated_at: { type: string } + current_version: { anyOf: [ { $ref: '#/components/schemas/use_cases_VersionView_response' }, { type: 'null' } ] } + draft: { anyOf: [ { $ref: '#/components/schemas/use_cases_DraftSummary_response' }, { type: 'null' } ] } additionalProperties: true - required: [id, project_id, reference_code, title, created_by, created_at, updated_at, current_version, draft] + required: [ id, project_id, reference_code, title, created_by, created_at, updated_at, current_version, draft ] use_cases_VersionView_response: type: object properties: - id: {type: integer} - version_number: {type: integer} - approval_state: {type: string} - title: {type: string} - primary_actor: - anyOf: - - {type: string} - - {type: 'null'} - change_note: - anyOf: - - {type: string} - - {type: 'null'} - created_by: - anyOf: - - {type: integer} - - {type: 'null'} - created_by_name: - anyOf: - - {type: string} - - {type: 'null'} - created_at: {type: string} - reviewed_by: - anyOf: - - {type: integer} - - {type: 'null'} - reviewed_at: - anyOf: - - {type: string} - - {type: 'null'} - approved_by: - anyOf: - - {type: integer} - - {type: 'null'} - approved_at: - anyOf: - - {type: string} - - {type: 'null'} - document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} - labels: - type: object - additionalProperties: {type: string} + id: { type: integer } + version_number: { type: integer } + approval_state: { type: string } + title: { type: string } + primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } + change_note: { anyOf: [ { type: string }, { type: 'null' } ] } + created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + created_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } + created_at: { type: string } + reviewed_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + reviewed_at: { anyOf: [ { type: string }, { type: 'null' } ] } + approved_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + approved_at: { anyOf: [ { type: string }, { type: 'null' } ] } + document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } + labels: { type: object, additionalProperties: { type: string } } additionalProperties: true - required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, - created_at, reviewed_by, reviewed_at, approved_by, approved_at, document, labels] + required: + [ + id, + version_number, + approval_state, + title, + primary_actor, + change_note, + created_by, + created_by_name, + created_at, + reviewed_by, + reviewed_at, + approved_by, + approved_at, + document, + labels + ] use_cases_VersionSummary_response: type: object properties: - id: {type: integer} - version_number: {type: integer} - approval_state: {type: string} - title: {type: string} - primary_actor: - anyOf: - - {type: string} - - {type: 'null'} - change_note: - anyOf: - - {type: string} - - {type: 'null'} - created_by: - anyOf: - - {type: integer} - - {type: 'null'} - created_by_name: - anyOf: - - {type: string} - - {type: 'null'} - created_at: {type: string} - reviewed_by: - anyOf: - - {type: integer} - - {type: 'null'} - reviewed_at: - anyOf: - - {type: string} - - {type: 'null'} - approved_by: - anyOf: - - {type: integer} - - {type: 'null'} - approved_at: - anyOf: - - {type: string} - - {type: 'null'} + id: { type: integer } + version_number: { type: integer } + approval_state: { type: string } + title: { type: string } + primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } + change_note: { anyOf: [ { type: string }, { type: 'null' } ] } + created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + created_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } + created_at: { type: string } + reviewed_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + reviewed_at: { anyOf: [ { type: string }, { type: 'null' } ] } + approved_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + approved_at: { anyOf: [ { type: string }, { type: 'null' } ] } additionalProperties: true - required: [id, version_number, approval_state, title, primary_actor, change_note, created_by, created_by_name, - created_at, reviewed_by, reviewed_at, approved_by, approved_at] + required: + [ + id, + version_number, + approval_state, + title, + primary_actor, + change_note, + created_by, + created_by_name, + created_at, + reviewed_by, + reviewed_at, + approved_by, + approved_at + ] use_cases_DraftSummary_response: type: object properties: - revision: {type: integer} - base_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by_name: - anyOf: - - {type: string} - - {type: 'null'} - updated_at: {type: string} + revision: { type: integer } + base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } + updated_at: { type: string } additionalProperties: true - required: [revision, base_version_id, updated_by, updated_by_name, updated_at] + required: [ revision, base_version_id, updated_by, updated_by_name, updated_at ] use_cases_DraftView_response: type: object properties: - use_case_id: {type: integer} - revision: {type: integer} - base_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by_name: - anyOf: - - {type: string} - - {type: 'null'} - updated_at: {type: string} - document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} - labels: - type: object - additionalProperties: {type: string} + use_case_id: { type: integer } + revision: { type: integer } + base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } + updated_at: { type: string } + document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } + labels: { type: object, additionalProperties: { type: string } } additionalProperties: true - required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels] + required: [ use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels ] use_cases_PutDraftRequest_request: type: object properties: - base_revision: - anyOf: - - {type: integer} - - {type: 'null'} - document: {$ref: '#/components/schemas/UseCaseDocument'} + base_revision: { anyOf: [ { type: integer }, { type: 'null' } ] } + document: { $ref: '#/components/schemas/UseCaseDocument' } additionalProperties: true - required: [document] + required: [ document ] use_cases_DraftChangeView_response: type: object properties: - use_case_id: {type: integer} - revision: {type: integer} - base_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by: - anyOf: - - {type: integer} - - {type: 'null'} - updated_by_name: - anyOf: - - {type: string} - - {type: 'null'} - updated_at: {type: string} - document: {$ref: '#/components/schemas/UseCaseDocumentResponse'} - labels: - type: object - additionalProperties: {type: string} - effects: - type: array - items: {$ref: '#/components/schemas/UseCaseEffect'} - additionalProperties: true - required: [use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels, - effects] - use_cases_SetApprovalRequest_request: - type: object - properties: - state: {type: string} + use_case_id: { type: integer } + revision: { type: integer } + base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } + updated_at: { type: string } + document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } + labels: { type: object, additionalProperties: { type: string } } + effects: { type: array, items: { $ref: '#/components/schemas/UseCaseEffect' } } additionalProperties: true - required: [state] + required: + [ + use_case_id, + revision, + base_version_id, + updated_by, + updated_by_name, + updated_at, + document, + labels, + effects + ] + use_cases_SetApprovalRequest_request: { type: object, properties: { state: { type: string } }, additionalProperties: true, required: [ state ] } use_cases_LinkView_response: type: object properties: - id: {type: integer} - element_id: - anyOf: - - {type: string} - - {type: 'null'} - element_label: - anyOf: - - {type: string} - - {type: 'null'} - target_type: - type: string - enum: [requirement, verification] - requirement_id: - anyOf: - - {type: integer} - - {type: 'null'} - verification_id: - anyOf: - - {type: integer} - - {type: 'null'} - target_reference_code: {type: string} - target_title: {type: string} - rationale: - anyOf: - - {type: string} - - {type: 'null'} - suspect: {type: boolean} - suspect_at: - anyOf: - - {type: string} - - {type: 'null'} - suspect_reason: - anyOf: - - {type: string} - - {type: 'null'} - linked_requirement_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - current_requirement_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - created_by: - anyOf: - - {type: integer} - - {type: 'null'} - created_at: {type: string} + id: { type: integer } + element_id: { anyOf: [ { type: string }, { type: 'null' } ] } + element_label: { anyOf: [ { type: string }, { type: 'null' } ] } + target_type: { type: string, enum: [ requirement, verification ] } + requirement_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + verification_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + target_reference_code: { type: string } + target_title: { type: string } + rationale: { anyOf: [ { type: string }, { type: 'null' } ] } + suspect: { type: boolean } + suspect_at: { anyOf: [ { type: string }, { type: 'null' } ] } + suspect_reason: { anyOf: [ { type: string }, { type: 'null' } ] } + linked_requirement_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + current_requirement_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } + created_at: { type: string } additionalProperties: true - required: [id, element_id, element_label, target_type, requirement_id, verification_id, target_reference_code, - target_title, rationale, suspect, suspect_at, suspect_reason, linked_requirement_version_id, current_requirement_version_id, - created_by, created_at] + required: + [ + id, + element_id, + element_label, + target_type, + requirement_id, + verification_id, + target_reference_code, + target_title, + rationale, + suspect, + suspect_at, + suspect_reason, + linked_requirement_version_id, + current_requirement_version_id, + created_by, + created_at + ] use_cases_ClearSuspectResponse_response: type: object - properties: - status: {type: string} - cleared: {type: boolean} + properties: { status: { type: string }, cleared: { type: boolean } } additionalProperties: true - required: [status, cleared] + required: [ status, cleared ] use_cases_UseCaseRef_response: type: object properties: - use_case_id: {type: integer} - reference_code: {type: string} - title: {type: string} - link_id: {type: integer} - element_id: - anyOf: - - {type: string} - - {type: 'null'} - element_label: - anyOf: - - {type: string} - - {type: 'null'} - suspect: {type: boolean} + use_case_id: { type: integer } + reference_code: { type: string } + title: { type: string } + link_id: { type: integer } + element_id: { anyOf: [ { type: string }, { type: 'null' } ] } + element_label: { anyOf: [ { type: string }, { type: 'null' } ] } + suspect: { type: boolean } additionalProperties: true - required: [use_case_id, reference_code, title, link_id, element_id, element_label, suspect] + required: [ use_case_id, reference_code, title, link_id, element_id, element_label, suspect ] use_cases_UseCaseCommentView_response: type: object properties: - id: {type: integer} - use_case_id: {type: integer} - use_case_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - element_id: - anyOf: - - {type: string} - - {type: 'null'} - element_label: - anyOf: - - {type: string} - - {type: 'null'} - author_id: {type: integer} - author_name: {type: string} - body: {type: string} - created_at: {type: string} + id: { type: integer } + use_case_id: { type: integer } + use_case_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + element_id: { anyOf: [ { type: string }, { type: 'null' } ] } + element_label: { anyOf: [ { type: string }, { type: 'null' } ] } + author_id: { type: integer } + author_name: { type: string } + body: { type: string } + created_at: { type: string } additionalProperties: true - required: [id, use_case_id, use_case_version_id, element_id, element_label, author_id, author_name, body, - created_at] + required: + [ + id, + use_case_id, + use_case_version_id, + element_id, + element_label, + author_id, + author_name, + body, + created_at + ] use_cases_BaselineUseCaseView_response: type: object properties: - use_case_id: {type: integer} - reference_code: {type: string} - title: {type: string} - primary_actor: - anyOf: - - {type: string} - - {type: 'null'} - version_id: {type: integer} - version_number: {type: integer} - approval_state: {type: string} - current_version_id: - anyOf: - - {type: integer} - - {type: 'null'} - current_version_number: - anyOf: - - {type: integer} - - {type: 'null'} - link_count: {type: integer} + use_case_id: { type: integer } + reference_code: { type: string } + title: { type: string } + primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } + version_id: { type: integer } + version_number: { type: integer } + approval_state: { type: string } + current_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } + current_version_number: { anyOf: [ { type: integer }, { type: 'null' } ] } + link_count: { type: integer } additionalProperties: true - required: [use_case_id, reference_code, title, primary_actor, version_id, version_number, approval_state, - current_version_id, current_version_number, link_count] + required: + [ + use_case_id, + reference_code, + title, + primary_actor, + version_id, + version_number, + approval_state, + current_version_id, + current_version_number, + link_count + ] ApiError: type: object - required: [status, error, message] - properties: - status: {type: integer} - error: {type: string} - message: {type: string} + required: [ status, error, message ] + properties: { status: { type: integer }, error: { type: string }, message: { type: string } } additionalProperties: true BuildInfo: type: object properties: - backend_version: &id006 {type: string} + backend_version: &id006 { type: string } backend_git_sha: *id006 - deployment_mode: - type: string - enum: [server, cloud] + deployment_mode: { type: string, enum: [ server, cloud ] } frontend_compatibility: type: object - properties: - min_version: *id006 - max_version: *id006 - required: [min_version, max_version] + properties: { min_version: *id006, max_version: *id006 } + required: [ min_version, max_version ] additionalProperties: true - required: [backend_version, backend_git_sha, deployment_mode, frontend_compatibility] + required: [ backend_version, backend_git_sha, deployment_mode, frontend_compatibility ] additionalProperties: true UseCaseElement: type: object - properties: - id: &id007 {type: string} - text: *id007 + properties: { id: &id007 { type: string }, text: *id007 } additionalProperties: false - required: [id, text] + required: [ id, text ] UseCaseBranchEnd: oneOf: - - type: object - properties: - type: {const: rejoin, type: string} - step: *id007 - additionalProperties: false - required: [type, step] - - type: object - properties: - type: {const: end, type: string} - outcome: - type: string - enum: [success, failure] - additionalProperties: false - required: [type, outcome] + - type: object + properties: { type: { const: rejoin, type: string }, step: *id007 } + additionalProperties: false + required: [ type, step ] + - type: object + properties: { type: { const: end, type: string }, outcome: { type: string, enum: [ success, failure ] } } + additionalProperties: false + required: [ type, outcome ] UseCaseDocument: type: object properties: - schema: {type: string, const: marreq.use-case.v1} + schema: { type: string, const: marreq.use-case.v1 } title: *id007 - primary_actor: - anyOf: - - *id007 - - {type: 'null'} - secondary_actors: - type: array - items: *id007 - summary: - anyOf: - - *id007 - - {type: 'null'} - preconditions: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} - main: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} - branches: - type: array - items: {$ref: '#/components/schemas/UseCaseBranch'} - postconditions: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} + primary_actor: { anyOf: [ *id007, { type: 'null' } ] } + secondary_actors: { type: array, items: *id007 } + summary: { anyOf: [ *id007, { type: 'null' } ] } + preconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } + main: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } + branches: { type: array, items: { $ref: '#/components/schemas/UseCaseBranch' } } + postconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } additionalProperties: false - required: [title] + required: [ title ] UseCaseBranch: type: object properties: id: *id007 - kind: - type: string - enum: [alternative, exception] + kind: { type: string, enum: [ alternative, exception ] } title: *id007 from_step: *id007 - end: {$ref: '#/components/schemas/UseCaseBranchEnd'} - steps: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} + end: { $ref: '#/components/schemas/UseCaseBranchEnd' } + steps: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } additionalProperties: false - required: [id, kind, title, from_step, end] + required: [ id, kind, title, from_step, end ] UseCaseEffect: oneOf: - - type: object - properties: - type: {type: string, const: relabelled} - id: *id007 - from: *id007 - to: *id007 - additionalProperties: true - required: [type, id, from, to] - - type: object - properties: - type: {type: string, const: removed} - id: *id007 - additionalProperties: true - required: [type, id] - - type: object - properties: - type: {type: string, const: rebranched} - id: *id007 - from_step: *id007 - additionalProperties: true - required: [type, id, from_step] - - type: object - properties: - type: {type: string, const: created} - id: *id007 - additionalProperties: true - required: [type, id] + - type: object + properties: + type: { type: string, const: relabelled } + id: *id007 + from: *id007 + to: *id007 + additionalProperties: true + required: [ type, id, from, to ] + - type: object + properties: { type: { type: string, const: removed }, id: *id007 } + additionalProperties: true + required: [ type, id ] + - type: object + properties: { type: { type: string, const: rebranched }, id: *id007, from_step: *id007 } + additionalProperties: true + required: [ type, id, from_step ] + - type: object + properties: { type: { type: string, const: created }, id: *id007 } + additionalProperties: true + required: [ type, id ] DeploymentInfo: type: object properties: - mode: - type: string - enum: [server, cloud] - allows_self_registration: &id008 {type: boolean} + mode: { type: string, enum: [ server, cloud ] } + allows_self_registration: &id008 { type: boolean } requires_email_verification: *id008 allows_admin_promotion: *id008 assigns_personal_workspace: *id008 allows_self_administered_user_creation: *id008 allows_database_backup: *id008 - required: [mode, allows_self_registration, requires_email_verification, allows_admin_promotion, assigns_personal_workspace, - allows_self_administered_user_creation, allows_database_backup] + required: + [ + mode, + allows_self_registration, + requires_email_verification, + allows_admin_promotion, + assigns_personal_workspace, + allows_self_administered_user_creation, + allows_database_backup + ] additionalProperties: true McpPrincipalResponse: type: object properties: - user_id: {type: integer} - authentication_type: {type: string} - principal_id: {type: string} - client_id: - anyOf: - - {type: string} - - {type: 'null'} - grant_id: - anyOf: - - {type: integer} - - {type: 'null'} + user_id: { type: integer } + authentication_type: { type: string } + principal_id: { type: string } + client_id: { anyOf: [ { type: string }, { type: 'null' } ] } + grant_id: { anyOf: [ { type: integer }, { type: 'null' } ] } additionalProperties: true - required: [user_id, authentication_type, principal_id, client_id, grant_id] + required: [ user_id, authentication_type, principal_id, client_id, grant_id ] UseCaseDocumentResponse: type: object properties: - schema: {type: string, const: marreq.use-case.v1} - title: &id009 {type: string} - primary_actor: - anyOf: - - *id009 - - {type: 'null'} - secondary_actors: - type: array - items: *id009 - summary: - anyOf: - - *id009 - - {type: 'null'} - preconditions: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} - main: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} - branches: - type: array - items: {$ref: '#/components/schemas/UseCaseBranchResponse'} - postconditions: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} + schema: { type: string, const: marreq.use-case.v1 } + title: &id009 { type: string } + primary_actor: { anyOf: [ *id009, { type: 'null' } ] } + secondary_actors: { type: array, items: *id009 } + summary: { anyOf: [ *id009, { type: 'null' } ] } + preconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } + main: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } + branches: { type: array, items: { $ref: '#/components/schemas/UseCaseBranchResponse' } } + postconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } additionalProperties: false - required: [schema, title, primary_actor, secondary_actors, summary, preconditions, main, branches, postconditions] + required: + [ + schema, + title, + primary_actor, + secondary_actors, + summary, + preconditions, + main, + branches, + postconditions + ] UseCaseBranchResponse: type: object properties: - id: &id010 {type: string} - kind: - type: string - enum: [alternative, exception] + id: &id010 { type: string } + kind: { type: string, enum: [ alternative, exception ] } title: *id010 from_step: *id010 - end: {$ref: '#/components/schemas/UseCaseBranchEnd'} - steps: - type: array - items: {$ref: '#/components/schemas/UseCaseElement'} + end: { $ref: '#/components/schemas/UseCaseBranchEnd' } + steps: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } additionalProperties: false - required: [id, kind, title, from_step, end, steps] + required: [ id, kind, title, from_step, end, steps ] UseCaseListRef: oneOf: - - type: string - enum: [preconditions, main, postconditions] - - type: object - properties: - branch: *id002 - additionalProperties: true - required: [branch] + - { type: string, enum: [ preconditions, main, postconditions ] } + - type: object + properties: { branch: *id002 } + additionalProperties: true + required: [ branch ] UseCaseNewElement: type: object - properties: - id: - anyOf: - - *id002 - - {type: 'null'} - text: *id002 + properties: { id: { anyOf: [ *id002, { type: 'null' } ] }, text: *id002 } additionalProperties: false - required: [text] + required: [ text ] UseCaseNewBranch: type: object properties: - id: - anyOf: - - *id002 - - {type: 'null'} - kind: - type: string - enum: [alternative, exception] + id: { anyOf: [ *id002, { type: 'null' } ] } + kind: { type: string, enum: [ alternative, exception ] } title: *id002 from_step: *id002 - end: {$ref: '#/components/schemas/UseCaseBranchEnd'} - steps: - type: array - items: {$ref: '#/components/schemas/UseCaseNewElement'} + end: { $ref: '#/components/schemas/UseCaseBranchEnd' } + steps: { type: array, items: { $ref: '#/components/schemas/UseCaseNewElement' } } additionalProperties: false - required: [kind, title, from_step, end] + required: [ kind, title, from_step, end ] UseCaseDependents: oneOf: - - type: string - enum: [cascade] - - type: object - properties: - rebranch_to: *id002 - additionalProperties: true - required: [rebranch_to] + - { type: string, enum: [ cascade ] } + - type: object + properties: { rebranch_to: *id002 } + additionalProperties: true + required: [ rebranch_to ] UseCaseOperation: oneOf: - - type: object - properties: - op: {type: string, const: set_header} - title: - anyOf: - - *id002 - - {type: 'null'} - primary_actor: - anyOf: - - *id002 - - {type: 'null'} - secondary_actors: - anyOf: - - type: array - items: *id002 - - {type: 'null'} - summary: - anyOf: - - *id002 - - {type: 'null'} - additionalProperties: false - required: [op] - - type: object - properties: - op: {type: string, const: add_element} - list: {$ref: '#/components/schemas/UseCaseListRef'} - after: - anyOf: - - *id002 - - {type: 'null'} - element: {$ref: '#/components/schemas/UseCaseNewElement'} - additionalProperties: false - required: [op, list, element] - - type: object - properties: - op: {type: string, const: update_text} - id: *id002 - text: *id002 - additionalProperties: false - required: [op, id, text] - - type: object - properties: - op: {type: string, const: move_element} - id: *id002 - list: - anyOf: - - {$ref: '#/components/schemas/UseCaseListRef'} - - {type: 'null'} - after: - anyOf: - - *id002 - - {type: 'null'} - additionalProperties: false - required: [op, id] - - type: object - properties: - op: {type: string, const: remove_element} - id: *id002 - dependents: - anyOf: - - {$ref: '#/components/schemas/UseCaseDependents'} - - {type: 'null'} - additionalProperties: false - required: [op, id] - - type: object - properties: - op: {type: string, const: add_branch} - branch: {$ref: '#/components/schemas/UseCaseNewBranch'} - additionalProperties: false - required: [op, branch] - - type: object - properties: - op: {type: string, const: update_branch} - id: *id002 - title: - anyOf: - - *id002 - - {type: 'null'} - kind: - anyOf: - - type: string - enum: [alternative, exception] - - {type: 'null'} - from_step: - anyOf: - - *id002 - - {type: 'null'} - end: - anyOf: - - {$ref: '#/components/schemas/UseCaseBranchEnd'} - - {type: 'null'} - additionalProperties: false - required: [op, id] - - type: object - properties: - op: {type: string, const: remove_branch} - id: *id002 - additionalProperties: false - required: [op, id] + - type: object + properties: + op: { type: string, const: set_header } + title: { anyOf: [ *id002, { type: 'null' } ] } + primary_actor: { anyOf: [ *id002, { type: 'null' } ] } + secondary_actors: { anyOf: [ { type: array, items: *id002 }, { type: 'null' } ] } + summary: { anyOf: [ *id002, { type: 'null' } ] } + additionalProperties: false + required: [ op ] + - type: object + properties: + op: { type: string, const: add_element } + list: { $ref: '#/components/schemas/UseCaseListRef' } + after: { anyOf: [ *id002, { type: 'null' } ] } + element: { $ref: '#/components/schemas/UseCaseNewElement' } + additionalProperties: false + required: [ op, list, element ] + - type: object + properties: { op: { type: string, const: update_text }, id: *id002, text: *id002 } + additionalProperties: false + required: [ op, id, text ] + - type: object + properties: + op: { type: string, const: move_element } + id: *id002 + list: { anyOf: [ { $ref: '#/components/schemas/UseCaseListRef' }, { type: 'null' } ] } + after: { anyOf: [ *id002, { type: 'null' } ] } + additionalProperties: false + required: [ op, id ] + - type: object + properties: + op: { type: string, const: remove_element } + id: *id002 + dependents: { anyOf: [ { $ref: '#/components/schemas/UseCaseDependents' }, { type: 'null' } ] } + additionalProperties: false + required: [ op, id ] + - type: object + properties: { op: { type: string, const: add_branch }, branch: { $ref: '#/components/schemas/UseCaseNewBranch' } } + additionalProperties: false + required: [ op, branch ] + - type: object + properties: + op: { type: string, const: update_branch } + id: *id002 + title: { anyOf: [ *id002, { type: 'null' } ] } + kind: { anyOf: [ { type: string, enum: [ alternative, exception ] }, { type: 'null' } ] } + from_step: { anyOf: [ *id002, { type: 'null' } ] } + end: { anyOf: [ { $ref: '#/components/schemas/UseCaseBranchEnd' }, { type: 'null' } ] } + additionalProperties: false + required: [ op, id ] + - type: object + properties: { op: { type: string, const: remove_branch }, id: *id002 } + additionalProperties: false + required: [ op, id ] UseCaseDiffSide: type: object properties: - list: - type: string - enum: [preconditions, main, postconditions, branch, branches] + list: { type: string, enum: [ preconditions, main, postconditions, branch, branches ] } branch_id: *id002 - label: - anyOf: - - *id002 - - {type: 'null'} + label: { anyOf: [ *id002, { type: 'null' } ] } text: *id002 from_step: *id002 - end: {$ref: '#/components/schemas/UseCaseBranchEnd'} - kind: - type: string - enum: [alternative, exception] + end: { $ref: '#/components/schemas/UseCaseBranchEnd' } + kind: { type: string, enum: [ alternative, exception ] } additionalProperties: true - required: [list, label, text] + required: [ list, label, text ] UseCaseElementDiff: type: object properties: id: *id002 - kind: - type: string - enum: [precondition, postcondition, step, branch] - changes: - type: array - items: - type: string - enum: [added, removed, edited, moved, rebranched] - old: - anyOf: - - {$ref: '#/components/schemas/UseCaseDiffSide'} - - {type: 'null'} - new: - anyOf: - - {$ref: '#/components/schemas/UseCaseDiffSide'} - - {type: 'null'} + kind: { type: string, enum: [ precondition, postcondition, step, branch ] } + changes: { type: array, items: { type: string, enum: [ added, removed, edited, moved, rebranched ] } } + old: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffSide' }, { type: 'null' } ] } + new: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffSide' }, { type: 'null' } ] } text_diff: type: array items: type: object - properties: - op: - type: string - enum: [equal, delete, insert] - text: *id002 + properties: { op: { type: string, enum: [ equal, delete, insert ] }, text: *id002 } additionalProperties: true - required: [op, text] + required: [ op, text ] additionalProperties: true - required: [id, kind, changes, old, new] + required: [ id, kind, changes, old, new ] UseCaseDiffEnd: type: object properties: - version_id: - anyOf: - - *id003 - - {type: 'null'} - version_number: - anyOf: - - *id003 - - {type: 'null'} - draft_revision: - anyOf: - - *id003 - - {type: 'null'} + version_id: { anyOf: [ *id003, { type: 'null' } ] } + version_number: { anyOf: [ *id003, { type: 'null' } ] } + draft_revision: { anyOf: [ *id003, { type: 'null' } ] } additionalProperties: true - required: [version_id, version_number, draft_revision] + required: [ version_id, version_number, draft_revision ] UseCaseDiff: type: object properties: - from: - anyOf: - - {$ref: '#/components/schemas/UseCaseDiffEnd'} - - {type: 'null'} - to: {$ref: '#/components/schemas/UseCaseDiffEnd'} + from: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffEnd' }, { type: 'null' } ] } + to: { $ref: '#/components/schemas/UseCaseDiffEnd' } summary: type: object properties: @@ -5646,41 +9102,29 @@ components: moved: *id003 rebranched: *id003 additionalProperties: true - required: [added, removed, edited, moved, rebranched] + required: [ added, removed, edited, moved, rebranched ] header: type: object additionalProperties: type: object properties: - old: &id011 - oneOf: - - *id002 - - {type: 'null'} - - type: array - items: *id002 + old: &id011 { oneOf: [ *id002, { type: 'null' }, { type: array, items: *id002 } ] } new: *id011 additionalProperties: true - required: [old, new] - elements: - type: array - items: {$ref: '#/components/schemas/UseCaseElementDiff'} + required: [ old, new ] + elements: { type: array, items: { $ref: '#/components/schemas/UseCaseElementDiff' } } additionalProperties: true - required: [from, to, summary, header, elements] + required: [ from, to, summary, header, elements ] UseCasePublishResponse: type: object properties: - version: {$ref: '#/components/schemas/use_cases_VersionView_response'} - removed_links: - type: array - items: {$ref: '#/components/schemas/use_cases_LinkView_response'} + version: { $ref: '#/components/schemas/use_cases_VersionView_response' } + removed_links: { type: array, items: { $ref: '#/components/schemas/use_cases_LinkView_response' } } additionalProperties: true - required: [version, removed_links] + required: [ version, removed_links ] responses: ApiError: description: API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. - content: - application/json: - schema: {$ref: '#/components/schemas/ApiError'} -security: -- cookieAuth: [] + content: { application/json: { schema: { $ref: '#/components/schemas/ApiError' } } } +security: [ { cookieAuth: [] } ] diff --git a/docs/releases.md b/docs/releases.md index cdcf1714..24ed5c07 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -161,5 +161,6 @@ previous plan unchanged or synthesize a manifest from current `main`; missing historical evidence deliberately prevents publication. No global release migration or production deployment is performed here. -The PR remains draft until required consumer schema gaps and security failures are -resolved; a document-only check is not a merge/release-readiness assertion. +A document-only check is not a release-readiness assertion. Publication also +requires passing consumer/handler tests and a provisioned immutable plan with +genuine historical bundles. Missing release evidence must continue to fail closed. From b80ef2cf58e81a918c51ead892c5e2443db3c538 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Wed, 4 Nov 2026 19:35:57 +0100 Subject: [PATCH 07/18] test(contracts): bind HTTP handlers and harden fail-closed discovery --- .github/workflows/marreq-ci.yml | 6 +- Cargo.lock | 1 + marreq-cloud/tests/api_contract_inventory.rs | 151 ++++++++++++++++ marreq-core/Cargo.toml | 1 + .../it/api_applicability_integration_test.rs | 4 +- marreq-core/tests/it/api_attachments_test.rs | 4 +- .../tests/it/api_authentication_test.rs | 4 +- .../tests/it/api_cache_integration_test.rs | 4 +- .../it/api_categories_integration_test.rs | 4 +- .../tests/it/api_constraint_violation_test.rs | 4 +- marreq-core/tests/it/api_dsm_test.rs | 4 +- .../tests/it/api_error_consistency_test.rs | 4 +- .../tests/it/api_exports_integration_test.rs | 4 +- .../tests/it/api_imports_integration_test.rs | 4 +- .../tests/it/api_matrix_endpoint_test.rs | 4 +- marreq-core/tests/it/api_mcp_phase2_test.rs | 4 +- .../tests/it/api_project_bundle_files_test.rs | 4 +- .../tests/it/api_project_bundle_test.rs | 4 +- marreq-core/tests/it/api_project_edit_test.rs | 4 +- .../tests/it/api_project_scoping_test.rs | 4 +- marreq-core/tests/it/api_reqifz_test.rs | 4 +- .../it/api_requirements_integration_test.rs | 4 +- .../tests/it/api_semantic_search_test.rs | 2 +- .../tests/it/api_status_integration_test.rs | 4 +- .../tests/it/api_tests_integration_test.rs | 4 +- .../tests/it/api_users_integration_test.rs | 4 +- marreq-core/tests/it/api_validation_test.rs | 4 +- .../tests/it/api_workflow_integration_test.rs | 4 +- marreq-core/tests/it/contract.rs | 127 ++++++++++++++ marreq-core/tests/it/cors_test.rs | 4 +- marreq-core/tests/it/csrf_integration_test.rs | 4 +- marreq-core/tests/it/main.rs | 2 + scripts/generate-api-types.mjs | 4 +- tools/marreq-contracts/src/artifact.rs | 15 +- tools/marreq-contracts/src/contract.rs | 12 +- tools/marreq-contracts/src/coverage.rs | 148 ++++++++++++++-- tools/marreq-contracts/src/main.rs | 7 +- tools/marreq-contracts/src/traffic.rs | 58 +++--- tools/marreq-contracts/tests/compatibility.rs | 166 +++++++++++++++++- 39 files changed, 696 insertions(+), 104 deletions(-) create mode 100644 marreq-core/tests/it/contract.rs diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index 01715c4e..cf1f64fe 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -37,8 +37,10 @@ jobs: npm ci --prefix mcp-server - name: Check generated consumer schemas run: node scripts/generate-api-types.mjs --check - - name: Verify compiled Server/Cloud operations - run: cargo test --locked -p marreq-cloud --test api_contract_inventory + - name: Verify compiled routes and handler wire contracts + run: | + cargo test --locked -p marreq-cloud --test api_contract_inventory + cargo test --locked -p marreq-core --features test-helpers --test it - name: Exercise isolated real Server/Cloud and HTTP consumers run: scripts/test-api-contracts.sh - name: Require verified contracts for every current consumer operation diff --git a/Cargo.lock b/Cargo.lock index c72f9d47..68faf023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3283,6 +3283,7 @@ dependencies = [ "infer 0.22.0", "lazy_static", "lettre", + "marreq-contracts", "oauth2", "openidconnect", "pgvector", diff --git a/marreq-cloud/tests/api_contract_inventory.rs b/marreq-cloud/tests/api_contract_inventory.rs index 1b989a0f..ffd647db 100644 --- a/marreq-cloud/tests/api_contract_inventory.rs +++ b/marreq-cloud/tests/api_contract_inventory.rs @@ -149,3 +149,154 @@ fn attachment_dtos_preserve_nullable_fields_and_storage_limits() { assert!(marreq_contracts::contract::validate_instance(&spec, schema, &broken).is_err()); } } + +fn request_model(operation_id: &str, value: serde_json::Value) { + let spec = spec(); + let op = marreq_contracts::contract::operations(&spec, None) + .unwrap() + .into_values() + .find(|op| op["operationId"] == operation_id) + .unwrap(); + let schema = op + .pointer("/requestBody/content/application~1json/schema") + .unwrap(); + marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + assert!( + serde_json::from_value::(value.clone()).is_ok(), + "{operation_id}: real Rust request rejected its fixture" + ); + // The declared wire language must not accept missing/null/wrongly typed + // fields which the actual handler's Serde type would reject. + for key in value.as_object().unwrap().keys() { + let mut omitted = value.clone(); + omitted.as_object_mut().unwrap().remove(key); + let mut candidates = vec![omitted]; + for replacement in [ + serde_json::Value::Null, + serde_json::json!(false), + serde_json::json!(42), + serde_json::json!({}), + serde_json::json!([]), + ] { + let mut changed = value.clone(); + changed[key] = replacement; + candidates.push(changed); + } + for candidate in candidates { + if marreq_contracts::contract::validate_instance(&spec, schema, &candidate).is_ok() { + assert!( + serde_json::from_value::(candidate).is_ok(), + "{operation_id}: contract accepts an invalid Rust field {key}" + ); + } + } + } +} + +#[test] +fn consumed_json_requests_match_real_handler_deserializers() { + use marreq_core::{api, models}; + use serde_json::json; + let tagged = json!({"title":"General","description":"Description","tag":"GEN","project_id":1}); + request_model::("categories_create", tagged.clone()); + request_model::("applicability_create", tagged.clone()); + request_model::( + "verification_methods_create_by_project", + tagged.clone(), + ); + request_model::( + "status_create_requirement_status", + tagged.clone(), + ); + request_model::( + "verification_status_create_verification_status", + tagged, + ); + request_model::( + "users_create", + json!({"username":"user","name":"Name","email":"user@example.test","password":"test-only","is_admin":false}), + ); + request_model::( + "users_update", + json!({"username":"user","name":"Name","email":"user@example.test","is_admin":false}), + ); + request_model::( + "groups_create", + json!({"name":"Group","description":null,"owner_id":null}), + ); + request_model::( + "custom_fields_create", + json!({"label":"Field","field_type":"text","enum_values":null,"sort_order":null}), + ); + request_model::( + "saved_views_create", + json!({"name":"View","visibility":"private","definition":{}}), + ); + request_model::( + "projects_create", + json!({"name":"Project"}), + ); + request_model::( + "projects_update", + json!({"name":"Project","description":null,"status":"Active","owner_id":1,"group_id":null}), + ); + request_model::( + "requirements_create_by_project", + json!({"title":"Title","description":"Description","author_id":1,"category_id":1,"status_id":1,"reference_code":"REQ-1","reviewer_id":1,"applicability_id":1,"project_id":1,"verification_method_ids":[1]}), + ); + request_model::( + "requirements_patch_by_project", + json!({"title":"Changed","status_id":1,"custom_fields":[]}), + ); + request_model::( + "verifications_create_by_project", + json!({"name":"Verification","reference_code":"VER-1","description":"Description","source":"manual","status_id":1,"project_id":1,"author_id":1,"reviewer_id":1}), + ); + request_model::( + "comments_create_by_project", + json!({"body":"Comment","requirement_version_id":null}), + ); + request_model::( + "baselines_create", + json!({"name":"Baseline","saved_view_id":null}), + ); + request_model::( + "mcp_internal_audit", + json!({"tool_name":"list_projects","is_write":false}), + ); +} + +#[test] +fn report_defaults_and_section_catalog_match_the_contract() { + use marreq_core::reports::{ + definition::{DefaultContext, ReportDefinition, ReportType}, + sections, + }; + let spec = spec(); + let context = DefaultContext { + project_name: "Project".into(), + project_slug: "project".into(), + user_name: "Tester".into(), + today: "2026-11-03".into(), + }; + for kind in ReportType::ALL { + let value = serde_json::to_value(ReportDefinition::default_for(kind, &context)).unwrap(); + for name in ["ReportDefinition", "ReportDefinitionInput"] { + marreq_contracts::contract::validate_instance( + &spec, + &spec["components"]["schemas"][name], + &value, + ) + .unwrap(); + } + } + for section in sections::catalog() { + let value = serde_json::to_value(section).unwrap(); + marreq_contracts::contract::validate_instance( + &spec, + &spec["components"]["schemas"]["ReportSectionSpec"], + &value, + ) + .unwrap(); + } +} diff --git a/marreq-core/Cargo.toml b/marreq-core/Cargo.toml index 231a13a3..5a9eb40c 100644 --- a/marreq-core/Cargo.toml +++ b/marreq-core/Cargo.toml @@ -58,6 +58,7 @@ flate2 = { workspace = true } tar = { workspace = true } [dev-dependencies] +marreq-contracts = { path = "../tools/marreq-contracts" } # 0.10 is not released yet; its `sha2`/`rand_core` re-exports match rsa's own # digest and RNG trait versions. rsa = { version = "0.9", features = ["getrandom", "sha2"] } diff --git a/marreq-core/tests/it/api_applicability_integration_test.rs b/marreq-core/tests/it/api_applicability_integration_test.rs index a0eff67b..cb699fbc 100644 --- a/marreq-core/tests/it/api_applicability_integration_test.rs +++ b/marreq-core/tests/it/api_applicability_integration_test.rs @@ -49,7 +49,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_attachments_test.rs b/marreq-core/tests/it/api_attachments_test.rs index 6279cf96..af9a965b 100644 --- a/marreq-core/tests/it/api_attachments_test.rs +++ b/marreq-core/tests/it/api_attachments_test.rs @@ -166,7 +166,9 @@ async fn harness() -> Harness { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); Harness { - client: Client::tracked(rocket).await.expect("rocket instance"), + client: crate::contract::tracked(rocket) + .await + .expect("rocket instance"), dir, } } diff --git a/marreq-core/tests/it/api_authentication_test.rs b/marreq-core/tests/it/api_authentication_test.rs index d41e94ce..812ab78d 100644 --- a/marreq-core/tests/it/api_authentication_test.rs +++ b/marreq-core/tests/it/api_authentication_test.rs @@ -59,7 +59,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_cache_integration_test.rs b/marreq-core/tests/it/api_cache_integration_test.rs index 311e1ff5..c4ae81f1 100644 --- a/marreq-core/tests/it/api_cache_integration_test.rs +++ b/marreq-core/tests/it/api_cache_integration_test.rs @@ -44,7 +44,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn admin_session_cookie(client: &Client) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_categories_integration_test.rs b/marreq-core/tests/it/api_categories_integration_test.rs index be4b89b1..39497424 100644 --- a/marreq-core/tests/it/api_categories_integration_test.rs +++ b/marreq-core/tests/it/api_categories_integration_test.rs @@ -49,7 +49,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_constraint_violation_test.rs b/marreq-core/tests/it/api_constraint_violation_test.rs index e08b9bac..d53fc99e 100644 --- a/marreq-core/tests/it/api_constraint_violation_test.rs +++ b/marreq-core/tests/it/api_constraint_violation_test.rs @@ -53,7 +53,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_dsm_test.rs b/marreq-core/tests/it/api_dsm_test.rs index cef900d7..4aef6b9b 100644 --- a/marreq-core/tests/it/api_dsm_test.rs +++ b/marreq-core/tests/it/api_dsm_test.rs @@ -175,7 +175,9 @@ async fn client() -> Client { .manage(marreq_core::auth::AuthConfig::default()) .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } fn session(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_error_consistency_test.rs b/marreq-core/tests/it/api_error_consistency_test.rs index 7d038e96..90bc12a6 100644 --- a/marreq-core/tests/it/api_error_consistency_test.rs +++ b/marreq-core/tests/it/api_error_consistency_test.rs @@ -48,7 +48,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_exports_integration_test.rs b/marreq-core/tests/it/api_exports_integration_test.rs index c0df40aa..500f81db 100644 --- a/marreq-core/tests/it/api_exports_integration_test.rs +++ b/marreq-core/tests/it/api_exports_integration_test.rs @@ -40,7 +40,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_imports_integration_test.rs b/marreq-core/tests/it/api_imports_integration_test.rs index 461a843c..9a82a305 100644 --- a/marreq-core/tests/it/api_imports_integration_test.rs +++ b/marreq-core/tests/it/api_imports_integration_test.rs @@ -41,7 +41,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_matrix_endpoint_test.rs b/marreq-core/tests/it/api_matrix_endpoint_test.rs index 90a034ff..2fd89147 100644 --- a/marreq-core/tests/it/api_matrix_endpoint_test.rs +++ b/marreq-core/tests/it/api_matrix_endpoint_test.rs @@ -49,7 +49,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_mcp_phase2_test.rs b/marreq-core/tests/it/api_mcp_phase2_test.rs index 10acd8c4..923db906 100644 --- a/marreq-core/tests/it/api_mcp_phase2_test.rs +++ b/marreq-core/tests/it/api_mcp_phase2_test.rs @@ -49,7 +49,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_project_bundle_files_test.rs b/marreq-core/tests/it/api_project_bundle_files_test.rs index 5c69a049..7d3a23fd 100644 --- a/marreq-core/tests/it/api_project_bundle_files_test.rs +++ b/marreq-core/tests/it/api_project_bundle_files_test.rs @@ -55,7 +55,9 @@ async fn harness(max_file_bytes: u64, quota_bytes: u64) -> Harness { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); Harness { - client: Client::tracked(rocket).await.expect("rocket instance"), + client: crate::contract::tracked(rocket) + .await + .expect("rocket instance"), dir, } } diff --git a/marreq-core/tests/it/api_project_bundle_test.rs b/marreq-core/tests/it/api_project_bundle_test.rs index ee8f3d52..07432356 100644 --- a/marreq-core/tests/it/api_project_bundle_test.rs +++ b/marreq-core/tests/it/api_project_bundle_test.rs @@ -41,7 +41,9 @@ pub(crate) mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_project_edit_test.rs b/marreq-core/tests/it/api_project_edit_test.rs index 028078c4..a6bb100b 100644 --- a/marreq-core/tests/it/api_project_edit_test.rs +++ b/marreq-core/tests/it/api_project_edit_test.rs @@ -70,7 +70,9 @@ async fn client() -> Client { .manage(marreq_core::auth::AuthConfig::default()) .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } fn session(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_project_scoping_test.rs b/marreq-core/tests/it/api_project_scoping_test.rs index a46891ca..5c3fbc10 100644 --- a/marreq-core/tests/it/api_project_scoping_test.rs +++ b/marreq-core/tests/it/api_project_scoping_test.rs @@ -50,7 +50,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_reqifz_test.rs b/marreq-core/tests/it/api_reqifz_test.rs index ca9fa714..461753db 100644 --- a/marreq-core/tests/it/api_reqifz_test.rs +++ b/marreq-core/tests/it/api_reqifz_test.rs @@ -221,7 +221,9 @@ async fn harness() -> Harness { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); Harness { - client: Client::tracked(rocket).await.expect("rocket instance"), + client: crate::contract::tracked(rocket) + .await + .expect("rocket instance"), dir, } } diff --git a/marreq-core/tests/it/api_requirements_integration_test.rs b/marreq-core/tests/it/api_requirements_integration_test.rs index 2f9991ec..87ffcbbb 100644 --- a/marreq-core/tests/it/api_requirements_integration_test.rs +++ b/marreq-core/tests/it/api_requirements_integration_test.rs @@ -49,7 +49,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_semantic_search_test.rs b/marreq-core/tests/it/api_semantic_search_test.rs index 65927474..c9c58c43 100644 --- a/marreq-core/tests/it/api_semantic_search_test.rs +++ b/marreq-core/tests/it/api_semantic_search_test.rs @@ -30,7 +30,7 @@ async fn client_with_repo(repo: DieselRepoMock) -> Client { .manage(marreq_core::auth::AuthConfig::default()) .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.unwrap() + crate::contract::tracked(rocket).await.unwrap() } fn auth_cookie(client: &Client) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_status_integration_test.rs b/marreq-core/tests/it/api_status_integration_test.rs index 89ae9944..cc1137aa 100644 --- a/marreq-core/tests/it/api_status_integration_test.rs +++ b/marreq-core/tests/it/api_status_integration_test.rs @@ -39,7 +39,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn admin_session_cookie(client: &Client) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_tests_integration_test.rs b/marreq-core/tests/it/api_tests_integration_test.rs index dcde5b2e..bfe26e64 100644 --- a/marreq-core/tests/it/api_tests_integration_test.rs +++ b/marreq-core/tests/it/api_tests_integration_test.rs @@ -50,7 +50,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_users_integration_test.rs b/marreq-core/tests/it/api_users_integration_test.rs index e5b19107..b040e730 100644 --- a/marreq-core/tests/it/api_users_integration_test.rs +++ b/marreq-core/tests/it/api_users_integration_test.rs @@ -40,7 +40,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_validation_test.rs b/marreq-core/tests/it/api_validation_test.rs index 76ec4891..a61ab369 100644 --- a/marreq-core/tests/it/api_validation_test.rs +++ b/marreq-core/tests/it/api_validation_test.rs @@ -50,7 +50,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn session_cookie(client: &Client, user_id: i32) -> Cookie<'static> { diff --git a/marreq-core/tests/it/api_workflow_integration_test.rs b/marreq-core/tests/it/api_workflow_integration_test.rs index 141c3cc4..78c5e955 100644 --- a/marreq-core/tests/it/api_workflow_integration_test.rs +++ b/marreq-core/tests/it/api_workflow_integration_test.rs @@ -40,7 +40,9 @@ mod test_support { .manage(marreq_core::auth::rate_limiter::LoginRateLimiter::new()) .mount("/api", marreq_core::api::routes()); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } pub fn base_repo() -> DieselRepoMock { diff --git a/marreq-core/tests/it/contract.rs b/marreq-core/tests/it/contract.rs new file mode 100644 index 00000000..7e8ae8be --- /dev/null +++ b/marreq-core/tests/it/contract.rs @@ -0,0 +1,127 @@ +//! Bind the existing Rocket integration scenarios to the published wire contract. +//! No separate response fixtures or route inventory are maintained here. +use marreq_contracts::{contract, coverage}; +use rocket::fairing::{Fairing, Info, Kind}; +use rocket::local::asynchronous::Client; +use rocket::{Build, Request, Response, Rocket}; +use serde_json::Value; +use std::{io::Cursor, sync::OnceLock}; + +fn spec() -> &'static Value { + static SPEC: OnceLock = OnceLock::new(); + SPEC.get_or_init(|| { + contract::load( + &std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../docs/developer/openapi.yaml"), + ) + .unwrap() + }) +} + +fn operation(request: &Request<'_>) -> Option { + let mut matches: Vec<_> = contract::operations(spec(), None) + .unwrap() + .into_iter() + .filter(|(key, _)| { + key.method.eq_ignore_ascii_case(request.method().as_str()) && { + coverage::intersects( + request.uri().path().as_str(), + &coverage::route_pattern(&key.path), + ) + } + }) + .collect(); + matches.sort_by_key(|(key, _)| key.path.matches('{').count()); + matches + .into_iter() + .next() + .map(|(_, op)| op) + .filter(|op| op["x-marreq-unverified"] != true) +} + +struct WireContract; + +#[rocket::async_trait] +impl Fairing for WireContract { + fn info(&self) -> Info { + Info { + name: "Consumer wire contract assertions", + kind: Kind::Response, + } + } + + async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { + let Some(op) = operation(request) else { + return; + }; + let status = response.status().code.to_string(); + let definition = op["responses"] + .get(&status) + .or_else(|| op["responses"].get("default")) + .unwrap_or_else(|| { + panic!( + "{} {}: undocumented HTTP {status}", + request.method(), + request.uri() + ) + }); + let definition = definition["$ref"] + .as_str() + .map_or(definition, |r| spec().pointer(&r[1..]).unwrap()); + let success = (200..300).contains(&response.status().code); + if success && let Some(headers) = definition["headers"].as_object() { + for (name, header) in headers { + if header["required"] == true { + assert!( + response.headers().contains(name), + "{}: missing {name}", + request.uri() + ); + } + } + } + let media = response.content_type().map(|t| t.media_type().to_string()); + let declared = definition.get("content").and_then(Value::as_object); + if success && let Some(declared) = declared { + assert!( + media.as_ref().is_some_and(|m| declared.contains_key(m)) + || declared.contains_key("*/*"), + "{} {}: undocumented successful media {media:?}", + request.method(), + request.uri() + ); + } + if media.as_deref() == Some("application/json") + && let Some(schema) = definition.pointer("/content/application~1json/schema") + { + let bytes = response.body_mut().to_bytes().await.unwrap(); + let value: Value = serde_json::from_slice(&bytes).unwrap(); + contract::validate_instance(spec(), schema, &value).unwrap_or_else(|e| { + panic!( + "{} {} HTTP {status}: {e:#}", + request.method(), + request.uri() + ) + }); + response.set_sized_body(bytes.len(), Cursor::new(bytes)); + } + } +} + +pub async fn tracked(rocket: Rocket) -> Result { + Client::tracked(rocket.attach(WireContract)).await +} + +#[rocket::get("/categories")] +fn incompatible_response() -> rocket::serde::json::Json { + rocket::serde::json::Json(serde_json::json!([{"title":"missing guaranteed fields"}])) +} + +#[rocket::async_test] +#[should_panic(expected = "wire schema violation")] +async fn response_assertions_reject_real_http_schema_drift() { + let client = tracked(rocket::build().mount("/api", rocket::routes![incompatible_response])) + .await + .unwrap(); + client.get("/api/categories").dispatch().await; +} diff --git a/marreq-core/tests/it/cors_test.rs b/marreq-core/tests/it/cors_test.rs index 4d075f42..72df48ba 100644 --- a/marreq-core/tests/it/cors_test.rs +++ b/marreq-core/tests/it/cors_test.rs @@ -39,7 +39,9 @@ async fn make_client(allowed_origins: &[&str], allow_credentials: bool) -> Clien let rocket = rocket::build() .attach(CorsFairing(policy)) .mount("/", rocket::routes![ping]); - Client::tracked(rocket).await.expect("rocket instance") + crate::contract::tracked(rocket) + .await + .expect("rocket instance") } const ALLOWED: &str = "https://app.example.com"; diff --git a/marreq-core/tests/it/csrf_integration_test.rs b/marreq-core/tests/it/csrf_integration_test.rs index e282ce65..06c01e51 100644 --- a/marreq-core/tests/it/csrf_integration_test.rs +++ b/marreq-core/tests/it/csrf_integration_test.rs @@ -69,7 +69,9 @@ async fn test_client() -> Client { ], ) .attach(CsrfFairing::new().with_origin(TEST_ALLOWED_ORIGIN)); - Client::tracked(rocket).await.expect("valid rocket") + crate::contract::tracked(rocket) + .await + .expect("valid rocket") } // --------------------------------------------------------------------------- diff --git a/marreq-core/tests/it/main.rs b/marreq-core/tests/it/main.rs index c5e436ab..26501cb1 100644 --- a/marreq-core/tests/it/main.rs +++ b/marreq-core/tests/it/main.rs @@ -5,6 +5,8 @@ #[macro_use] extern crate rocket; +mod contract; + mod api_applicability_integration_test; mod api_attachments_test; mod api_authentication_test; diff --git a/scripts/generate-api-types.mjs b/scripts/generate-api-types.mjs index e357f028..87c1ec8c 100644 --- a/scripts/generate-api-types.mjs +++ b/scripts/generate-api-types.mjs @@ -76,9 +76,7 @@ const directory = mkdtempSync(join(tmpdir(), 'marreq-types-')); try { for (const component of ['frontend', 'mcp']) { const path = join(directory, `${component}.json`); - // Inventory-only entries generate no invented types. Strict release/CI - // coverage is checked separately by `marreq-contracts validate`. - execFileSync('cargo', ['run', '--quiet', '--locked', '-p', 'marreq-contracts', '--', 'select', '--component', component, '--allow-unverified', '--output', path]); + execFileSync('cargo', ['run', '--quiet', '--locked', '-p', 'marreq-contracts', '--', 'select', '--component', component, '--output', path]); const spec = JSON.parse(readFileSync(path, 'utf8')); spec.paths = {}; // Clients currently import schemas, not generated SDK paths. const ast = await openapiTS(spec, { alphabetize: true }); diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs index a5e12f7e..22155b8e 100644 --- a/tools/marreq-contracts/src/artifact.rs +++ b/tools/marreq-contracts/src/artifact.rs @@ -155,7 +155,7 @@ pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> } else { coverage::scan(root, who, &spec)?.operations }; - let selected = contract::select(&spec, &required, mode.as_deref(), true)?; + let selected = contract::select(&spec, &required, mode.as_deref())?; if who == "backend" { contract::validate(&selected)?; } else { @@ -178,16 +178,9 @@ pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> .map(String::from), ); } else { - inputs.extend( - [ - "src", - "package.json", - "package-lock.json", - "compatibility.json", - ] - .into_iter() - .map(|path| format!("{directory}/{path}")), - ); + // Build configuration (for example Vite's API routing) belongs to the + // exact consumer source evidence too, not just src/ and package files. + inputs.push(directory.into()); } let status = Command::new("git") .args(["status", "--porcelain", "--untracked-files=all", "--"]) diff --git a/tools/marreq-contracts/src/contract.rs b/tools/marreq-contracts/src/contract.rs index 56908314..b09123ac 100644 --- a/tools/marreq-contracts/src/contract.rs +++ b/tools/marreq-contracts/src/contract.rs @@ -72,7 +72,6 @@ pub fn select( spec: &Value, required: &BTreeSet, variant: Option<&str>, - verified: bool, ) -> Result { let available = operations(spec, variant)?; let mut selected = json!({"openapi":spec["openapi"],"info":spec["info"],"servers":spec.get("servers").cloned().unwrap_or(json!([{ "url":"/" }])),"paths":{},"components":{}}); @@ -83,10 +82,9 @@ pub fn select( variant.unwrap_or("selected") ) })?; - if verified - && op - .get("x-marreq-unverified") - .is_some_and(|v| v != &Value::Bool(false)) + if op + .get("x-marreq-unverified") + .is_some_and(|v| v != &Value::Bool(false)) { bail!("required consumer operation has unverified wire contract: {key}"); } @@ -315,8 +313,8 @@ pub fn validate_wire_contract(document: &Value) -> Result<()> { pub fn compare(consumer: &Value, backend: &Value, variant: &str) -> Result { crate::artifact::variant(variant)?; let required = operations(consumer, Some(variant))?.into_keys().collect(); - let old = select(consumer, &required, Some(variant), true)?; - let new = select(backend, &required, Some(variant), true)?; + let old = select(consumer, &required, Some(variant))?; + let new = select(backend, &required, Some(variant))?; validate_wire_contract(&old)?; validate_wire_contract(&new)?; // These are Marreq-specific authorization semantics not standard OpenAPI diff --git a/tools/marreq-contracts/src/coverage.rs b/tools/marreq-contracts/src/coverage.rs index e88e175d..52d609e8 100644 --- a/tools/marreq-contracts/src/coverage.rs +++ b/tools/marreq-contracts/src/coverage.rs @@ -193,6 +193,9 @@ fn values( return vec!["*".into()]; }; let fun_name = name(fun, source); + if text(fun, source) == "URL.createObjectURL" { + return vec!["blob:*".into()]; + } let args = node .child_by_field_name("arguments") .map(children) @@ -366,7 +369,7 @@ pub fn intersects(a: &str, b: &str) -> bool { } go(a.as_bytes(), b.as_bytes(), 0, 0, &mut HashSet::new()) } -fn path_glob(path: &str) -> String { +pub fn route_pattern(path: &str) -> String { let mut out = String::new(); let mut param = false; for c in path.chars() { @@ -388,7 +391,23 @@ pub fn analyze( declared: &BTreeMap, transport: bool, ) -> Result { - analyze_file(source, tsx, declared, transport, false) + analyze_file(source, tsx, declared, transport, None) +} + +fn navigation(node: Node<'_>, source: &str) -> bool { + let object = node.child_by_field_name("object").map(|n| text(n, source)); + match name(node, source).as_str() { + "assign" | "replace" | "href" => { + object.is_some_and(|s| s == "location" || s.ends_with(".location")) + } + "location" => object == Some("window"), + "open" => object == Some("window"), + _ => false, + } +} + +fn api_navigation(path: &str) -> bool { + path.starts_with("/api/") || path.starts_with("/oauth/") || path.starts_with("/.well-known/") } fn analyze_file( @@ -396,7 +415,7 @@ fn analyze_file( tsx: bool, declared: &BTreeMap, transport: bool, - user_content_links: bool, + source_path: Option<&str>, ) -> Result { let mut parser = tree_sitter::Parser::new(); parser.set_language(&if tsx { @@ -427,6 +446,45 @@ fn analyze_file( ); } } + // Alternate networking implementations need an adapter instead of escaping + // the shared-transport checks with an unresolved dynamic URL. + let mut constructors = vec![]; + descend(root, "new_expression", &mut constructors); + for expression in constructors { + let constructor = name(field(expression, "constructor")?, source); + anyhow::ensure!( + !matches!( + constructor.as_str(), + "XMLHttpRequest" | "EventSource" | "WebSocket" + ), + "{constructor} needs an explicit contract transport adapter" + ); + } + let mut import_statements = vec![]; + descend(root, "import_statement", &mut import_statements); + for import in import_statements { + if text(import, source).starts_with("import type ") { + continue; + } + if let Some(module) = import.child_by_field_name("source") { + let module = text(module, source).trim_matches(['\'', '"']); + anyhow::ensure!( + !matches!( + module, + "axios" + | "ky" + | "got" + | "undici" + | "superagent" + | "http" + | "https" + | "node:http" + | "node:https" + ), + "network module {module} needs an explicit contract transport adapter" + ); + } + } let mut calls = vec![]; descend(root, "call_expression", &mut calls); let mut result = Coverage { @@ -470,8 +528,17 @@ fn analyze_file( } for call in calls { let fun = field(call, "function")?; + let is_navigation = navigation(fun, source); let fun_name = name(fun, source); - let fun_name = aliases.get(&fun_name).cloned().unwrap_or(fun_name); + let fun_name = if is_navigation { + "fetch".into() + } else { + aliases.get(&fun_name).cloned().unwrap_or(fun_name) + }; + anyhow::ensure!( + fun_name != "sendBeacon", + "sendBeacon needs an explicit contract transport adapter" + ); if !matches!( fun_name.as_str(), "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" @@ -481,10 +548,13 @@ fn analyze_file( .map(children) .unwrap_or_default(); let mut endpoints = vec![]; - if let Some(first) = args.first() { - endpoints.extend(values(*first, source, root, &BTreeMap::new(), 0)); - if first.kind() == "object" { - for pair in children(*first).into_iter().filter(|n| n.kind() == "pair") { + for argument in &args { + endpoints.extend(values(*argument, source, root, &BTreeMap::new(), 0)); + if argument.kind() == "object" { + for pair in children(*argument) + .into_iter() + .filter(|n| n.kind() == "pair") + { if pair .child_by_field_name("key") .is_some_and(|n| matches!(text(n, source), "url" | "path" | "uri")) @@ -559,6 +629,21 @@ fn analyze_file( bail!("HTTP call has no path") }; let mut paths = values(*path, source, root, &BTreeMap::new(), 0); + if is_navigation { + // These existing flows navigate to user-selected UI paths or the + // external provider URL returned by the audited identity-link API. + // Neither is a new, statically chosen application HTTP operation. + let explicit_flow = (source_path == Some("frontend/src/pages/LoginPage.tsx") + && text(*path, source) == "returnTo") + || (source_path == Some("frontend/src/pages/AccountPage.tsx") + && text(*path, source).starts_with("await startIdentityLink(")); + if paths == ["*"] && explicit_flow { + continue; + } + if paths.iter().all(|p| p != "*" && !api_navigation(p)) { + continue; + } + } // A forwarding download helper is covered at each concrete call site. if fun_name == "fetchDownload" && paths == ["*"] @@ -575,7 +660,7 @@ fn analyze_file( paths.sort(); paths.dedup(); let method = methods( - if fun_name == "download" { + if fun_name == "download" || is_navigation { None } else { args.get(1).copied() @@ -584,7 +669,7 @@ fn analyze_file( ) .with_context(|| format!("line {}", call.start_position().row + 1))?; for path in paths { - if !path.starts_with("/api/") { + if !api_navigation(&path) { bail!( "unresolved HTTP dependency at line {}: {}", call.start_position().row + 1, @@ -594,7 +679,9 @@ fn analyze_file( for method in &method { let found: Vec<_> = declared .keys() - .filter(|op| op.method == *method && intersects(&path, &path_glob(&op.path))) + .filter(|op| { + op.method == *method && intersects(&path, &route_pattern(&op.path)) + }) .cloned() .collect(); if found.is_empty() { @@ -626,7 +713,9 @@ fn analyze_file( } else { *value }; - if user_content_links && text(value, source) == "n.href" { + if source_path == Some("frontend/src/components/StatementText.tsx") + && text(value, source) == "n.href" + { continue; } for path in values(value, source, root, &BTreeMap::new(), 0) { @@ -637,12 +726,12 @@ fn analyze_file( attribute.start_position().row + 1 ); } - if !path.starts_with("/api/") { + if !api_navigation(path) { continue; } let keys: Vec<_> = declared .keys() - .filter(|key| key.method == "get" && intersects(path, &path_glob(&key.path))) + .filter(|key| key.method == "get" && intersects(path, &route_pattern(&key.path))) .cloned() .collect(); if keys.is_empty() { @@ -651,6 +740,35 @@ fn analyze_file( result.operations.extend(keys); } } + let mut assignments = vec![]; + descend(root, "assignment_expression", &mut assignments); + for assignment in assignments { + if !navigation(field(assignment, "left")?, source) { + continue; + } + for path in values( + field(assignment, "right")?, + source, + root, + &BTreeMap::new(), + 0, + ) { + let path = path.split('?').next().unwrap(); + anyhow::ensure!(path != "*", "unresolved browser navigation assignment"); + if api_navigation(path) { + let required: Vec<_> = declared + .keys() + .filter(|op| op.method == "get" && intersects(path, &route_pattern(&op.path))) + .cloned() + .collect(); + anyhow::ensure!( + !required.is_empty(), + "undocumented browser API dependency {path}" + ); + result.operations.extend(required); + } + } + } Ok(result) } @@ -695,7 +813,7 @@ pub fn scan(root: &Path, component: &str, spec: &Value) -> Result { path.extension().is_some_and(|x| x == "tsx"), &declared, transport, - relative == "frontend/src/components/StatementText.tsx", + Some(relative.as_ref()), ) .with_context(|| relative.to_string())?; result.call_sites += coverage.call_sites; diff --git a/tools/marreq-contracts/src/main.rs b/tools/marreq-contracts/src/main.rs index 0f47dc2c..405459a0 100644 --- a/tools/marreq-contracts/src/main.rs +++ b/tools/marreq-contracts/src/main.rs @@ -62,8 +62,6 @@ enum Action { spec: PathBuf, #[arg(long)] output: PathBuf, - #[arg(long)] - allow_unverified: bool, }, Compare { consumer: PathBuf, @@ -133,13 +131,12 @@ fn run() -> anyhow::Result { component, spec, output, - allow_unverified, } => { let spec = contract::load(&spec)?; let coverage = coverage::scan(&root, &component, &spec)?; contract::write( &output, - &contract::select(&spec, &coverage.operations, None, !allow_unverified)?, + &contract::select(&spec, &coverage.operations, None)?, )?; Ok( serde_json::json!({"component":component,"operations":coverage.operations.len(),"output":output}), @@ -169,7 +166,7 @@ fn run() -> anyhow::Result { if !document_only { for who in ["frontend", "mcp"] { let c = coverage::scan(&root, who, &spec)?; - let selected = contract::select(&spec, &c.operations, None, true)?; + let selected = contract::select(&spec, &c.operations, None)?; contract::validate_wire_contract(&selected)?; counts.insert(who.into(), serde_json::json!(c.operations.len())); } diff --git a/tools/marreq-contracts/src/traffic.rs b/tools/marreq-contracts/src/traffic.rs index 03f73598..f044ba8b 100644 --- a/tools/marreq-contracts/src/traffic.rs +++ b/tools/marreq-contracts/src/traffic.rs @@ -28,7 +28,8 @@ pub fn verify(spec: &Value, path: &Path) -> Result { let mut matches: Vec<_> = operations .iter() .filter(|(key, _)| { - key.method == method && coverage::intersects(actual, &route_pattern(&key.path)) + key.method == method + && coverage::intersects(actual, &coverage::route_pattern(&key.path)) }) .collect(); matches.sort_by_key(|(key, _)| key.path.matches('{').count()); @@ -65,39 +66,30 @@ pub fn verify(spec: &Value, path: &Path) -> Result { contract::validate_instance(spec, schema, &record["request"])?; } checked += 1; - seen.insert((key.method.clone(), key.path.clone(), status)); + seen.insert(( + mode.to_owned(), + key.method.clone(), + key.path.clone(), + status, + )); } - for (method, path, status) in [ - ("get", "/api/meta/build", "200"), - ("get", "/api/meta/deployment", "200"), - ("post", "/api/projects/{project_id}/use-cases", "201"), - ( - "put", - "/api/projects/{project_id}/use-cases/{id}/draft", - "200", - ), - ("get", "/api/projects/{project_id}/use-cases", "403"), - ] { - ensure!( - seen.contains(&(method.into(), path.into(), status.into())), - "critical real-backend evidence missing: {method} {path} {status}" - ); - } - Ok(json!({"validated_exchanges":checked,"unverified_exchanges":unverified})) -} -fn route_pattern(path: &str) -> String { - let mut out = String::new(); - let mut param = false; - for c in path.chars() { - match c { - '{' => { - param = true; - out.push('*'); - } - '}' => param = false, - _ if !param => out.push(c), - _ => (), + for mode in ["server", "cloud"] { + for (method, path, status) in [ + ("get", "/api/meta/build", "200"), + ("get", "/api/meta/deployment", "200"), + ("post", "/api/projects/{project_id}/use-cases", "201"), + ( + "put", + "/api/projects/{project_id}/use-cases/{id}/draft", + "200", + ), + ("get", "/api/projects/{project_id}/use-cases", "403"), + ] { + ensure!( + seen.contains(&(mode.into(), method.into(), path.into(), status.into())), + "critical {mode} real-backend evidence missing: {method} {path} {status}" + ); } } - out + Ok(json!({"validated_exchanges":checked,"unverified_exchanges":unverified})) } diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs index 70203315..f1f2c91e 100644 --- a/tools/marreq-contracts/tests/compatibility.rs +++ b/tools/marreq-contracts/tests/compatibility.rs @@ -123,7 +123,7 @@ fn unrelated_backend_endpoint_can_change() { method: "post".into(), path: "/api/things".into(), }]); - let consumer = contract::select(&old, &required, None, true).unwrap(); + let consumer = contract::select(&old, &required, None).unwrap(); contract::compare(&consumer, &new, "server").unwrap(); } #[test] @@ -384,7 +384,7 @@ fn dynamic_unverified_dependency_is_rejected() { false, ) .unwrap(); - assert!(contract::select(&doc, &coverage.operations, None, true).is_err()); + assert!(contract::select(&doc, &coverage.operations, None).is_err()); } #[test] fn segment_globs_do_not_capture_unrelated_children() { @@ -485,6 +485,13 @@ fn aliases_and_unrecognized_clients_cannot_hide_new_calls() { "import {fetchJson as api} from './transport'; api('/api/missing');", "axios.get('/api/missing');", "axios({url:'/api/missing'});", + "client.open('POST', '/api/missing');", + "new XMLHttpRequest();", + "new EventSource(endpoint);", + "new WebSocket(endpoint);", + "navigator.sendBeacon(endpoint, data);", + "import client from 'axios'; client.get(endpoint);", + "import {request} from 'node:https';", ] { assert!( coverage::analyze(source, false, &ops, false).is_err(), @@ -539,3 +546,158 @@ fn relative_plan_paths_work() { String::from_utf8_lossy(&result.stdout) ); } + +#[test] +fn browser_calls_and_assignments_cannot_hide_dependencies() { + let mut document = spec(); + document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); + let operations = contract::operations(&document, None).unwrap(); + for source in [ + "window.location.assign('/api/missing')", + "window.open('/api/missing')", + "window.location.href='/api/missing'", + "window.location.href=unknown", + "window.location.assign(unknown)", + ] { + assert!( + coverage::analyze(source, false, &operations, false).is_err(), + "{source}" + ); + } + for source in [ + "window.location.assign('/api/things')", + "window.open('/api/things', '_blank')", + "window.location.href='/api/things'", + ] { + assert_eq!( + coverage::analyze(source, false, &operations, false) + .unwrap() + .operations + .len(), + 1, + "{source}" + ); + } + assert!( + coverage::analyze( + "const url=URL.createObjectURL(blob);window.location.href=url", + false, + &operations, + false + ) + .unwrap() + .operations + .is_empty() + ); +} + +#[test] +fn consumer_bundle_requires_committed_build_configuration() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path(); + std::fs::create_dir_all(root.join("docs/developer")).unwrap(); + std::fs::create_dir_all(root.join("frontend/src")).unwrap(); + std::fs::copy( + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/developer/openapi.yaml"), + root.join("docs/developer/openapi.yaml"), + ) + .unwrap(); + std::fs::write( + root.join("frontend/src/client.ts"), + "fetch('/api/meta/build');", + ) + .unwrap(); + std::fs::write( + root.join("frontend/compatibility.json"), + r#"{"requires_backend_min":"0.2.0","requires_backend_max":"0.2.99"}"#, + ) + .unwrap(); + std::fs::write(root.join("frontend/vite.config.ts"), "export default {};").unwrap(); + for args in [ + vec!["init", "--quiet"], + vec!["add", "."], + vec![ + "-c", + "user.name=Contract Test", + "-c", + "user.email=contract@example.test", + "commit", + "--quiet", + "-m", + "fixture", + ], + ] { + assert!( + Command::new("git") + .args(args) + .current_dir(root) + .status() + .unwrap() + .success() + ); + } + let version = "0.2.1".parse().unwrap(); + let clean = artifact::export(root, "frontend", version, None).unwrap(); + assert_eq!(clean.source_sha.len(), 40); + std::fs::write( + root.join("frontend/vite.config.ts"), + "export default { define: {api:'/changed'} };", + ) + .unwrap(); + let error = artifact::export(root, "frontend", "0.2.1".parse().unwrap(), None).unwrap_err(); + assert!(error.to_string().contains("commit source/contract inputs")); +} + +#[test] +fn traffic_requires_both_real_backend_variants() { + let mut document = spec(); + document["paths"] = json!({}); + let targets = [ + ("get", "/api/meta/build", 200), + ("get", "/api/meta/deployment", 200), + ("post", "/api/projects/{project_id}/use-cases", 201), + ( + "put", + "/api/projects/{project_id}/use-cases/{id}/draft", + 200, + ), + ("get", "/api/projects/{project_id}/use-cases", 403), + ]; + let mut records = vec![]; + for (method, path, status) in targets { + document["paths"][path][method] = json!({"responses":{status.to_string():{"description":"fixture","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"}}}}}}}}); + for variant in ["server", "cloud"] { + records.push(json!({"variant":variant,"method":method,"path":path.replace("{project_id}","1").replace("{id}","2"),"status":status,"response":{"id":1}})); + } + } + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("traffic.jsonl"); + let write = |records: &[Value]| { + std::fs::write( + &path, + records + .iter() + .map(Value::to_string) + .collect::>() + .join("\n"), + ) + .unwrap() + }; + write(&records); + marreq_contracts::traffic::verify(&document, &path).unwrap(); + let server_only: Vec<_> = records + .iter() + .filter(|r| r["variant"] == "server") + .cloned() + .collect(); + write(&server_only); + assert!( + marreq_contracts::traffic::verify(&document, &path) + .unwrap_err() + .to_string() + .contains("cloud real-backend evidence missing") + ); + records[0]["response"]["id"] = json!("wrong type"); + write(&records); + assert!(marreq_contracts::traffic::verify(&document, &path).is_err()); +} From 949bcf2eaacbad1a4f7c8544b263220d589666c1 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Wed, 4 Nov 2026 19:36:55 +0100 Subject: [PATCH 08/18] test(frontend): await default report state after template deletion --- frontend/src/pages/__tests__/ReportBuilderPage.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx b/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx index 191dde0a..314765fd 100644 --- a/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx +++ b/frontend/src/pages/__tests__/ReportBuilderPage.test.tsx @@ -157,6 +157,8 @@ describe('ReportBuilderPage', () => { await userEvent.click(screen.getByRole('button', { name: 'Delete' })); expect(apiClient.deleteReportTemplate).toHaveBeenCalledWith(5, 7, 'csrf'); await waitFor(() => expect(screen.getByTestId('where')).toHaveTextContent('?type=vcd')); + // The route changes before the effect restores the default definition. + await waitFor(() => expect(sectionTitles()).toEqual(['Cover page', 'Contents', 'Verification control matrix'])); await userEvent.click(screen.getByRole('checkbox', { name: 'Include Contents' })); await screen.findByText('Unsaved changes.'); From 397d7ed0f76fb75921a8e6b56603e8af0df85399 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Wed, 4 Nov 2026 19:41:43 +0100 Subject: [PATCH 09/18] fix(tests): box Rocket launch errors for current Clippy --- marreq-core/tests/it/contract.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/marreq-core/tests/it/contract.rs b/marreq-core/tests/it/contract.rs index 7e8ae8be..aa86c58b 100644 --- a/marreq-core/tests/it/contract.rs +++ b/marreq-core/tests/it/contract.rs @@ -108,8 +108,10 @@ impl Fairing for WireContract { } } -pub async fn tracked(rocket: Rocket) -> Result { - Client::tracked(rocket.attach(WireContract)).await +pub async fn tracked(rocket: Rocket) -> Result> { + Client::tracked(rocket.attach(WireContract)) + .await + .map_err(Box::new) } #[rocket::get("/categories")] From e8334bbd3b4acbc779aefdf8ce32cf671bf15a30 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:35:18 +0100 Subject: [PATCH 10/18] fix(docker): include contracts workspace member and test production images --- .github/workflows/marreq-ci.yml | 32 ++++++++++++++++++++++++++++++++ docker/Dockerfile | 25 ++++++++++++++----------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index cf1f64fe..bf318b65 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -166,6 +166,9 @@ jobs: working-directory: frontend run: npm run build + - name: Build independent frontend image + run: docker build -f docker/frontend/Dockerfile -t marreq-frontend:ci . + backend-build: runs-on: ubuntu-latest steps: @@ -185,6 +188,35 @@ jobs: - name: Build workspace (release) run: cargo build --workspace --release + backend-images: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + binary: [marreq-server, marreq-cloud] + steps: + - uses: actions/checkout@v7 + - uses: docker/setup-buildx-action@v4 + - name: Build production backend image + uses: docker/build-push-action@v7 + with: + context: . + file: docker/Dockerfile + load: true + tags: ${{ matrix.binary }}:ci + build-args: MARREQ_BIN=${{ matrix.binary }} + cache-from: type=gha,scope=${{ matrix.binary }} + cache-to: type=gha,mode=max,scope=${{ matrix.binary }} + - name: Verify runtime excludes contract tooling + run: | + docker run --rm --entrypoint sh ${{ matrix.binary }}:ci -ec ' + test ! -e /app/tools + test ! -e /app/docs + ! command -v marreq-contracts + ! command -v oasdiff + ! command -v node + ' + mcp-tests: runs-on: ubuntu-latest steps: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0de03a8e..c35715a5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,6 @@ # Build stage FROM rust:1-trixie AS builder -ARG MARREQ_BIN=marreq-server -ARG MARREQ_VERSION= -ARG MARREQ_GIT_SHA=unknown -ENV MARREQ_GIT_SHA=${MARREQ_GIT_SHA} -ENV MARREQ_VERSION=${MARREQ_VERSION} - # C toolchain for native crates (aws-lc-sys via rustls, openssl-sys, pq-sys) RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ @@ -20,31 +14,40 @@ COPY Cargo.toml Cargo.lock ./ COPY marreq-core/Cargo.toml marreq-core/diesel.toml ./marreq-core/ COPY marreq-server/Cargo.toml ./marreq-server/ COPY marreq-cloud/Cargo.toml ./marreq-cloud/ +COPY tools/marreq-contracts/Cargo.toml ./tools/marreq-contracts/ # Dummy sources so cargo can resolve and cache dependencies -RUN mkdir -p marreq-core/src marreq-server/src marreq-cloud/src && \ +RUN mkdir -p marreq-core/src marreq-server/src marreq-cloud/src tools/marreq-contracts/src && \ echo "fn main() {}" > marreq-server/src/main.rs && \ echo "fn main() {}" > marreq-cloud/src/main.rs && \ - echo "" > marreq-core/src/lib.rs + echo "" > marreq-core/src/lib.rs && \ + echo "" > tools/marreq-contracts/src/lib.rs && \ + echo "fn main() {}" > tools/marreq-contracts/src/main.rs # Cache dependencies only (ignore build errors from empty stubs) -RUN cargo build --release -p ${MARREQ_BIN} 2>/dev/null || true +RUN cargo build --locked --release -p marreq-server -p marreq-cloud 2>/dev/null || true # Install diesel_cli for migrations (postgres only) RUN cargo install diesel_cli --no-default-features --features postgres # Copy real sources -RUN rm -rf marreq-core/src marreq-server/src marreq-cloud/src +RUN rm -rf marreq-core/src marreq-server/src marreq-cloud/src tools/marreq-contracts/src COPY marreq-core/src ./marreq-core/src COPY marreq-core/frontend_compatibility.json ./marreq-core/frontend_compatibility.json COPY marreq-core/migrations ./marreq-core/migrations COPY marreq-core/scripts ./marreq-core/scripts COPY marreq-server/src ./marreq-server/src COPY marreq-cloud/src ./marreq-cloud/src +COPY tools/marreq-contracts/src ./tools/marreq-contracts/src # Build the selected binary (MARREQ_VERSION / MARREQ_GIT_SHA baked via option_env!) +ARG MARREQ_BIN=marreq-server +ARG MARREQ_VERSION= +ARG MARREQ_GIT_SHA=unknown +ENV MARREQ_GIT_SHA=${MARREQ_GIT_SHA} +ENV MARREQ_VERSION=${MARREQ_VERSION} RUN touch marreq-core/src/lib.rs marreq-server/src/main.rs marreq-cloud/src/main.rs && \ - cargo build --release -p ${MARREQ_BIN} + cargo build --locked --release -p ${MARREQ_BIN} # Runtime stage FROM debian:trixie-slim From 464f8b624cd547b710a298c90b6a4cf989a77f5a Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:40:19 +0100 Subject: [PATCH 11/18] refactor(openapi): bundle authoritative domain modules with pinned offline Redocly --- .github/workflows/marreq-ci.yml | 10 +- .github/workflows/release-artifacts.yml | 10 +- .gitignore | 6 + docs/developer/openapi.yaml | 9130 ----------------- .../openapi/components/responses.yaml | 6 + .../openapi/components/securitySchemes.yaml | 16 + docs/developer/openapi/paths/admin.yaml | 368 + docs/developer/openapi/paths/attachments.yaml | 301 + docs/developer/openapi/paths/auth.yaml | 585 ++ docs/developer/openapi/paths/baselines.yaml | 368 + docs/developer/openapi/paths/catalog.yaml | 528 + docs/developer/openapi/paths/mcp.yaml | 87 + .../openapi/paths/notifications.yaml | 223 + .../openapi/paths/project-settings.yaml | 344 + docs/developer/openapi/paths/projects.yaml | 636 ++ docs/developer/openapi/paths/reports.yaml | 239 + .../openapi/paths/requirement-history.yaml | 390 + .../developer/openapi/paths/requirements.yaml | 563 + .../developer/openapi/paths/traceability.yaml | 369 + docs/developer/openapi/paths/transfers.yaml | 602 ++ .../openapi/paths/use-case-links.yaml | 350 + docs/developer/openapi/paths/use-cases.yaml | 675 ++ docs/developer/openapi/paths/users.yaml | 511 + .../openapi/paths/verifications.yaml | 791 ++ docs/developer/openapi/root.yaml | 612 ++ docs/developer/openapi/schemas/admin.yaml | 229 + .../openapi/schemas/attachments.yaml | 70 + docs/developer/openapi/schemas/auth.yaml | 141 + docs/developer/openapi/schemas/baselines.yaml | 289 + docs/developer/openapi/schemas/catalog.yaml | 211 + docs/developer/openapi/schemas/common.yaml | 155 + docs/developer/openapi/schemas/mcp.yaml | 82 + docs/developer/openapi/schemas/projects.yaml | 458 + docs/developer/openapi/schemas/reports.yaml | 213 + .../openapi/schemas/requirements.yaml | 376 + .../openapi/schemas/traceability.yaml | 252 + .../openapi/schemas/use-case-views.yaml | 545 + docs/developer/openapi/schemas/use-cases.yaml | 501 + docs/developer/openapi/schemas/users.yaml | 85 + .../openapi/schemas/verifications.yaml | 134 + marreq-cloud/tests/api_contract_inventory.rs | 38 +- marreq-core/tests/it/contract.rs | 4 +- package-lock.json | 1 + package.json | 1 + scripts/bundle-openapi.mjs | 41 + tools/marreq-contracts/src/artifact.rs | 9 +- tools/marreq-contracts/src/lib.rs | 1 + tools/marreq-contracts/src/main.rs | 18 +- tools/marreq-contracts/src/source.rs | 22 + tools/marreq-contracts/tests/compatibility.rs | 13 +- 50 files changed, 12439 insertions(+), 9170 deletions(-) delete mode 100644 docs/developer/openapi.yaml create mode 100644 docs/developer/openapi/components/responses.yaml create mode 100644 docs/developer/openapi/components/securitySchemes.yaml create mode 100644 docs/developer/openapi/paths/admin.yaml create mode 100644 docs/developer/openapi/paths/attachments.yaml create mode 100644 docs/developer/openapi/paths/auth.yaml create mode 100644 docs/developer/openapi/paths/baselines.yaml create mode 100644 docs/developer/openapi/paths/catalog.yaml create mode 100644 docs/developer/openapi/paths/mcp.yaml create mode 100644 docs/developer/openapi/paths/notifications.yaml create mode 100644 docs/developer/openapi/paths/project-settings.yaml create mode 100644 docs/developer/openapi/paths/projects.yaml create mode 100644 docs/developer/openapi/paths/reports.yaml create mode 100644 docs/developer/openapi/paths/requirement-history.yaml create mode 100644 docs/developer/openapi/paths/requirements.yaml create mode 100644 docs/developer/openapi/paths/traceability.yaml create mode 100644 docs/developer/openapi/paths/transfers.yaml create mode 100644 docs/developer/openapi/paths/use-case-links.yaml create mode 100644 docs/developer/openapi/paths/use-cases.yaml create mode 100644 docs/developer/openapi/paths/users.yaml create mode 100644 docs/developer/openapi/paths/verifications.yaml create mode 100644 docs/developer/openapi/root.yaml create mode 100644 docs/developer/openapi/schemas/admin.yaml create mode 100644 docs/developer/openapi/schemas/attachments.yaml create mode 100644 docs/developer/openapi/schemas/auth.yaml create mode 100644 docs/developer/openapi/schemas/baselines.yaml create mode 100644 docs/developer/openapi/schemas/catalog.yaml create mode 100644 docs/developer/openapi/schemas/common.yaml create mode 100644 docs/developer/openapi/schemas/mcp.yaml create mode 100644 docs/developer/openapi/schemas/projects.yaml create mode 100644 docs/developer/openapi/schemas/reports.yaml create mode 100644 docs/developer/openapi/schemas/requirements.yaml create mode 100644 docs/developer/openapi/schemas/traceability.yaml create mode 100644 docs/developer/openapi/schemas/use-case-views.yaml create mode 100644 docs/developer/openapi/schemas/use-cases.yaml create mode 100644 docs/developer/openapi/schemas/users.yaml create mode 100644 docs/developer/openapi/schemas/verifications.yaml create mode 100644 scripts/bundle-openapi.mjs create mode 100644 tools/marreq-contracts/src/source.rs diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index bf318b65..9c0edc5a 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -28,13 +28,13 @@ jobs: cache: npm - name: Install pinned standards tooling run: scripts/install-oasdiff.sh - - name: Test native compatibility semantics and CLI failures - run: cargo test --locked -p marreq-contracts - name: Install TypeScript tooling and consumers run: | npm ci npm ci --prefix frontend npm ci --prefix mcp-server + - name: Test native compatibility semantics and CLI failures + run: cargo test --locked -p marreq-contracts - name: Check generated consumer schemas run: node scripts/generate-api-types.mjs --check - name: Verify compiled routes and handler wire contracts @@ -322,6 +322,12 @@ jobs: MARREQ_TEST_DATABASE_URL: postgres://rust:rust@127.0.0.1:5433/marreq steps: - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: npm + - name: Install pinned OpenAPI bundler + run: npm ci - name: Free runner disk space run: | diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index c9bff04c..47193724 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -20,13 +20,21 @@ jobs: with: toolchain: stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - uses: actions/setup-node@v7 + with: + node-version: '22' + cache: npm + - name: Install pinned OpenAPI bundler + run: npm ci - name: Install pinned OpenAPI standards engine run: scripts/install-oasdiff.sh - name: Test contract semantics and validate producer document run: | cargo test --locked -p marreq-contracts cargo run --locked -p marreq-contracts -- validate --document-only - cargo test --locked -p marreq-cloud --test api_contract_inventory + - name: Verify candidate backend routes and serializers + if: startsWith(github.ref_name, 'marreq-server-v') + run: cargo test --locked -p marreq-cloud --test api_contract_inventory - name: Require immutable deployment-plan revision id: plan env: diff --git a/.gitignore b/.gitignore index 220456b8..5c472ee6 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,9 @@ _bmad-output/ .cursorignore AGENTS.md + +# Optional self-contained OpenAPI output; edit openapi/root.yaml and domains. +/docs/developer/openapi.yaml +# Rust source module, not a generated coverage report. +!/tools/marreq-contracts/src/coverage/ +!/tools/marreq-contracts/src/coverage/*.rs diff --git a/docs/developer/openapi.yaml b/docs/developer/openapi.yaml deleted file mode 100644 index 9db89146..00000000 --- a/docs/developer/openapi.yaml +++ /dev/null @@ -1,9130 +0,0 @@ -openapi: 3.1.0 -info: - { - title: Marreq HTTP API, - version: 0.1.0, - description: Consumer-facing HTTP contracts. Unconsumed backend routes are outside compatibility enforcement. - Required operations marked x-marreq-unverified fail verification; no missing schemas are inferred from - inventory entries. - } -servers: [ { url: / } ] -paths: - /api/cache/stats: - get: - operationId: cache_stats - description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session - administrator only; identical in Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CacheStats" } } } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } - "500": { $ref: "#/components/responses/ApiError" } - /api/cache/recommendations: - get: - operationId: cache_recommendations - description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session - administrator only; identical in Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CacheRecommendations" } } } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } - "500": { $ref: "#/components/responses/ApiError" } - /api/cache/clear: - post: - operationId: cache_clear - description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session - administrator only; identical in Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CacheActionResult" } } } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } - "500": { $ref: "#/components/responses/ApiError" } - x-marreq-csrf: session-csrf - /api/cache/cleanup: - post: - operationId: cache_cleanup - description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session - administrator only; identical in Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CacheCleanupResult" } } } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } - "500": { $ref: "#/components/responses/ApiError" } - x-marreq-csrf: session-csrf - /api/cache/reset-counters: - post: - operationId: cache_reset_counters - description: Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session - administrator only; identical in Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CacheActionResult" } } } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: "Not an administrator, or CSRF check failed; body may be framework-generated." } - "500": { $ref: "#/components/responses/ApiError" } - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{id}/activity: - get: - operationId: activity_requirement_activity_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/EntityActivityItem" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, - permission and project-boundary rules in Server and Cloud. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/verifications/{id}/activity: - get: - operationId: activity_verification_activity_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/EntityActivityItem" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, - permission and project-boundary rules in Server and Cloud. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/applicability: - get: - operationId: applicability_list - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Applicability" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ViewRequirements - post: - operationId: applicability_create - parameters: [] - responses: - "201": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/NewApplicability" } } } - } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/applicability/{id}: - put: - operationId: applicability_update - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string }, message: { type: string } } - required: [ status, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/NewApplicability" } } } - } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: applicability_delete - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/attachments: - get: - operationId: attachments_list - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - in: query - name: entity_type - required: true - schema: { type: string, enum: [ requirement, verification ] } - - { in: query, name: entity_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/AttachmentView" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "422": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;delegated-requirements-and-verifications-read - x-marreq-bearer-scopes: [ requirements:read, verifications:read ] - post: - operationId: attachments_upload - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "201": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "413": - description: Upload exceeds the file or project quota, or Rocket's request limit. The handler returns ApiError - JSON; Rocket can reject an oversized form before the handler with a framework-generated body. - "415": { $ref: "#/components/responses/ApiError" } - "422": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied - x-marreq-csrf: session-csrf - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: { type: string, format: binary } - entity_type: { type: string, enum: [ requirement, verification ] } - entity_id: { type: integer } - required: [ file, entity_type, entity_id ] - additionalProperties: true - /api/projects/{project_id}/attachments/{attachment_id}/download: - get: - operationId: attachments_download - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: attachment_id, required: true, schema: { type: integer } } - responses: - "200": - description: Stored file served as a download using its detected media type. - headers: - Content-Disposition: { required: true, schema: { type: string } } - X-Content-Type-Options: { required: true, schema: { type: string, const: nosniff } } - Content-Security-Policy: { required: true, schema: { type: string, const: default-src 'none'; sandbox } } - Cache-Control: { required: true, schema: { type: string, const: "private, no-cache" } } - content: { "*/*": { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied - /api/projects/{project_id}/storage: - get: - operationId: attachments_get_storage - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentStorage" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied - /api/projects/{project_id}/storage/quota: - put: - operationId: attachments_set_quota - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AttachmentStorage" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "422": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin;not-archived - x-marreq-csrf: session-csrf - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { quota_mb: { anyOf: [ { type: integer, minimum: 1, maximum: 1073741824 }, { type: "null" } ] } } - additionalProperties: false - /api/projects/{project_id}/baselines/{baseline_id}/attachments: - get: - operationId: attachments_list_for_baseline - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/AttachmentView" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read - x-marreq-bearer-scopes: [ baselines:read ] - /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: - get: - operationId: attachments_download_for_baseline - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - - { in: path, name: attachment_id, required: true, schema: { type: integer } } - responses: - "200": - description: Stored file served as a download using its detected media type. - headers: - Content-Disposition: { required: true, schema: { type: string } } - X-Content-Type-Options: { required: true, schema: { type: string, const: nosniff } } - Content-Security-Policy: { required: true, schema: { type: string, const: default-src 'none'; sandbox } } - Cache-Control: { required: true, schema: { type: string, const: "private, no-cache" } } - content: { "*/*": { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read - x-marreq-bearer-scopes: [ baselines:read ] - /api/auth/providers: - get: - operationId: auth_auth_providers - security: [] - parameters: [] - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/external_ProviderDiscovery_response' } } } - /api/auth/external/{provider}/start: - get: - operationId: auth_auth_external_start - parameters: - - { in: path, name: provider, required: true, schema: { type: string } } - - { in: query, name: return_to, schema: { type: string } } - responses: - "303": - description: Redirect to the provider or validated local return path. - headers: { Location: { required: true, schema: { type: string } } } - "400": { $ref: "#/components/responses/ApiError" } - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [] - /api/auth/external/{provider}/callback: - get: - operationId: auth_auth_external_callback - parameters: - - { in: path, name: provider, required: true, schema: { type: string } } - - { in: query, name: code, schema: { type: string } } - - { in: query, name: state, schema: { type: string } } - - { in: query, name: error, schema: { type: string } } - responses: - "303": - description: Redirect to the provider or validated local return path. - headers: { Location: { required: true, schema: { type: string } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": { $ref: "#/components/responses/ApiError" } - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [] - x-marreq-authorization: single-use-oauth-transaction;link-requires-original-session - /api/auth/identities: - get: - operationId: auth_auth_identities - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthIdentities" } } } - "401": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [ { cookieAuth: [] } ] - /api/auth/me: - get: - operationId: auth_auth_me - security: [ { cookieAuth: [] } ] - parameters: [] - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/entities_User_response' } } } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': - description: API error - content: { application/json: { schema: { $ref: '#/components/schemas/ApiError' } } } - put: - operationId: auth_auth_update_me - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/entities_User_response" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": { $ref: "#/components/responses/ApiError" } - "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and services/user_service.rs. Own profile only. Email changes require the - current password when configured; Cloud rejects email changes. Other profile changes work in both - variants. - security: [ { cookieAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/AuthProfileUpdate" } } } - } - x-marreq-csrf: session-csrf - /api/admin/backup/info: - get: - operationId: backup_info - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/BackupInfo" } } } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "410": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud - returns 410 because allows_database_backup is false. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin;allows_database_backup - /api/admin/backup: - post: - operationId: backup_create - parameters: [ { in: query, name: attachments, schema: { type: boolean }, required: false } ] - responses: - "200": - description: Server backup archive; Cloud returns 410. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/gzip: { schema: { type: string, format: binary } } } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "410": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud - returns 410 because allows_database_backup is false. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin;allows_database_backup - x-marreq-csrf: session-csrf - /api/projects/{project_id}/baselines: - get: - operationId: baselines_list - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Baseline" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - post: - operationId: baselines_create - parameters: - [ - { in: path, name: project_id, required: true, schema: { type: integer } }, - { - in: header, - name: Idempotency-Key, - required: false, - schema: { type: string }, - description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore - or hyphen. Reuse with a different payload returns 409." - } - ] - responses: - "200": - { - description: Success, - content: { application/json: { schema: { $ref: "#/components/schemas/Baseline" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - saved_view_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ name ] - additionalProperties: true - x-marreq-bearer-scopes: [ baselines:write ] - x-marreq-authorization: project-membership;EditRequirements;visible-saved-view;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/baselines/{baseline_id}: - get: - operationId: baselines_get - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - "200": - { - description: Success, - content: { application/json: { schema: { $ref: "#/components/schemas/Baseline" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/{baseline_id}/requirements: - get: - operationId: baselines_get_requirements - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Requirement" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/{baseline_id}/traceability: - get: - operationId: baselines_get_traceability - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - { - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/BaselineTraceability" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/{baseline_id}/verifications: - get: - operationId: baselines_get_verifications - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - { - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/BaselineVerification" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/diff: - get: - operationId: baselines_diff_baselines - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: baseline_a, schema: { type: integer }, required: true } - - { in: query, name: baseline_b, schema: { type: integer }, required: true } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/BaselineDiff" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable - source view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to - create. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/catalog: - get: - operationId: catalog_get - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - categories: { type: array, items: { $ref: "#/components/schemas/Category" } } - applicability: { type: array, items: { $ref: "#/components/schemas/Applicability" } } - requirement_statuses: { type: array, items: { $ref: "#/components/schemas/RequirementStatus" } } - verification_statuses: { type: array, items: { $ref: "#/components/schemas/VerificationStatus" } } - verification_methods: { type: array, items: { $ref: "#/components/schemas/VerificationMethod" } } - custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldDefinition" } } - required: - - categories - - applicability - - requirement_statuses - - verification_statuses - - verification_methods - - custom_fields - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/catalog.rs repository serializers. Project-scoped lookup arrays in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/categories: - get: - operationId: categories_list - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Category" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ViewRequirements - post: - operationId: categories_create - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/NewCategory" } } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/categories/{id}: - put: - operationId: categories_update - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string }, message: { type: string } } - required: [ status, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/NewCategory" } } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: categories_delete - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire - shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{requirement_id}/comments: - get: - operationId: comments_list_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - - { in: query, name: version_id, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CommentResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author - display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - post: - operationId: comments_create_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - - in: header - name: Idempotency-Key - required: false - schema: { type: string } - description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore - or hyphen. Reuse with a different payload returns 409. - responses: - "201": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CommentResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author - display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { body: { type: string }, requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } } - required: [ body ] - additionalProperties: true - x-marreq-bearer-scopes: [ requirements:write ] - x-marreq-authorization: project-membership;EditRequirements;not-archived;approved-version-comment-lock-policy - x-marreq-csrf: session-csrf - /api/requirements/{requirement_id}/comments: - get: - operationId: comments_list - parameters: - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - - { in: query, name: version_id, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CommentResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author - display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in - both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;ViewRequirements - post: - operationId: comments_create - parameters: [ { in: path, name: requirement_id, required: true, schema: { type: integer } } ] - responses: - "201": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/CommentResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author - display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in - both variants. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { body: { type: string }, requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } } - required: [ body ] - additionalProperties: true - x-marreq-authorization: session;EditRequirements;not-archived;approved-version-comment-lock-policy - x-marreq-csrf: session-csrf - /api/projects/{project_id}/custom_fields: - get: - operationId: custom_fields_list_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/CustomFieldDefinition" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field - validation apply in both variants; enum_values is arbitrary stored JSON or null. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - post: - operationId: custom_fields_create - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field - validation apply in both variants; enum_values is arbitrary stored JSON or null. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/CustomFieldDefinitionPayload" } } } - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/custom_fields/{field_id}: - put: - operationId: custom_fields_update - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: field_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string }, message: { type: string } } - required: [ status, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field - validation apply in both variants; enum_values is arbitrary stored JSON or null. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/CustomFieldDefinitionPayload" } } } - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: custom_fields_delete - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: field_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string }, message: { type: string } } - required: [ status, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field - validation apply in both variants; enum_values is arbitrary stored JSON or null. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/dsm: - get: - operationId: dsm_get_dsm - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: link_types, schema: { type: string }, required: false } - - { in: query, name: category_id, schema: { type: integer }, required: false } - - { in: query, name: root_id, schema: { type: integer }, required: false } - - { in: query, name: order, schema: { type: string, enum: [ hierarchy, partition ] }, required: false } - responses: - "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Dsm" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/dsm.rs and dsm_service.rs output structs. Comma-separated link types, optional - project category/root, hierarchy or partition order; numeric indices and loop paths are preserved. - Shared in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read, traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/exports/requirements.xlsx: - get: - operationId: exports_export_requirements_xlsx - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/verifications.xlsx: - get: - operationId: exports_export_verifications_xlsx - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/matrix.xlsx: - get: - operationId: exports_export_matrix_xlsx - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/dsm.xlsx: - get: - operationId: exports_export_dsm_xlsx - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: link_types, schema: { type: string }, required: false } - - { in: query, name: category_id, schema: { type: integer }, required: false } - - { in: query, name: root_id, schema: { type: integer }, required: false } - - { in: query, name: order, schema: { type: string, enum: [ hierarchy, partition ] }, required: false } - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/matrix-links.xlsx: - get: - operationId: exports_export_matrix_links_xlsx - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/requirements.pdf: - get: - operationId: exports_export_requirements_pdf - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/pdf: { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/report.pdf: - get: - operationId: exports_export_report_pdf - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/pdf: { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/requirements.reqif: - get: - operationId: exports_export_requirements_reqif - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/xml: { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/requirements.reqifz: - get: - operationId: exports_export_requirements_reqifz - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/zip: { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/baselines/{filename}: - get: - operationId: exports_export_baseline_reqif - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: filename, required: true, schema: { type: string } } - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/xml: { schema: { type: string, format: binary } } - application/zip: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/bundle.json: - get: - operationId: exports_export_project_bundle - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/json: { schema: { type: object } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/exports/bundle.zip: - get: - operationId: exports_export_project_bundle_zip - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: { application/zip: { schema: { type: string, format: binary } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the - same media and mandatory download filename header. Consumers treat file contents as opaque downloads; - the JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated - server-side. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/groups: - get: - operationId: groups_list - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup - post: - operationId: groups_create - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ name ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;authenticated - x-marreq-csrf: session-csrf - /api/groups/creatable: - get: - operationId: groups_list_creatable - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjects - /api/groups/{group_id}: - get: - operationId: groups_get - parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup - patch: - operationId: groups_update - parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/GroupResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { name: { type: string }, description: { anyOf: [ { type: string }, { type: "null" } ] } } - required: [ name ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers - x-marreq-csrf: session-csrf - delete: - operationId: groups_delete - parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers - x-marreq-csrf: session-csrf - /api/groups/{group_id}/projects: - get: - operationId: groups_list_projects - parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Project" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup - /api/groups/{group_id}/members: - get: - operationId: groups_list_members - parameters: [ { in: path, name: group_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/GroupMemberResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup - /api/groups/{group_id}/members/{user_id}: - put: - operationId: groups_set_member_role - parameters: - - { in: path, name: group_id, required: true, schema: { type: integer } } - - { in: path, name: user_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/GroupMemberResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: { type: object, properties: { role: { type: integer } }, required: [ role ], additionalProperties: true } - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers - x-marreq-csrf: session-csrf - delete: - operationId: groups_remove_member - parameters: - - { in: path, name: group_id, required: true, schema: { type: integer } } - - { in: path, name: user_id, required: true, schema: { type: integer } } - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session - or personal API token, delegated OAuth denied. Group membership and the operation-specific group - permission are enforced. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers - x-marreq-csrf: session-csrf - /api/home/attention: - get: - operationId: home_attention - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - items: { type: array, items: { $ref: "#/components/schemas/AttentionItem" } } - approvals: { type: integer } - reviews: { type: integer } - suspect_links: { type: integer } - notifications: { type: integer } - required: [ items, approvals, reviews, suspect_links, notifications ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/home.rs AttentionItem/AttentionResponse and ProjectsRead guard. Both variants; at - most 50 attention items, nullable entity fields are serialized explicitly. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ projects:read ] - x-marreq-authorization: current-project-membership;token-project-scope - /api/search: - get: - operationId: home_search - parameters: - - { in: query, name: q, schema: { type: string }, required: true } - - { in: query, name: limit, schema: { type: integer, minimum: 0 }, required: false } - responses: - "200": - description: Success - content: - application/json: - schema: - type: array - items: - type: object - properties: - kind: { type: string, enum: [ requirement, use_case ] } - project_id: { type: integer } - requirement_id: { anyOf: [ { type: integer }, { type: "null" } ] } - use_case_id: { anyOf: [ { type: integer }, { type: "null" } ] } - reference_code: { type: string } - title: { type: string } - required: [ kind, project_id, requirement_id, use_case_id, reference_code, title ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/home.rs SearchHit and search. Session-only in both variants; limit is clamped, short - queries yield an empty array. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;ViewRequirements - /api/projects/{project_id}/imports/excel/preview: - post: - operationId: imports_preview_excel - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - import_type: { type: string } - columns: - type: array - items: - type: object - properties: { index: { type: integer }, name: { type: string }, sample_value: { type: string } } - required: [ index, name, sample_value ] - additionalProperties: true - sample_rows: { type: array, items: { type: array, items: { type: string } } } - row_count: { type: integer } - available_fields: - type: object - properties: - requirements: { type: array, items: { type: string } } - tests: { type: array, items: { type: string } } - matrix: { type: array, items: { type: string } } - required: [ requirements, tests, matrix ] - additionalProperties: true - unique_values: { type: object, additionalProperties: { type: array, items: { type: string } } } - required: [ import_type, columns, sample_rows, row_count, available_fields, unique_values ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "413": { $ref: "#/components/responses/ApiError" } - "415": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. - Session/personal token; delegated OAuth denied. Both variants validate uploads and project - permissions. Size/content validation and Rocket multipart failures can return framework bodies. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: { file: { type: string, format: binary } } - required: [ file ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/imports/excel: - post: - operationId: imports_commit_excel - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/ExcelImportResult" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "413": { $ref: "#/components/responses/ApiError" } - "415": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. - Session/personal token; delegated OAuth denied. Both variants validate uploads and project - permissions. Size/content validation and Rocket multipart failures can return framework bodies. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: { type: string, format: binary } - import_type: { type: string } - column_mappings: { type: string } - value_mappings: { type: string } - required: [ file, import_type, column_mappings ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/imports/reqif: - post: - operationId: imports_commit_reqif - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - message: { type: string } - imported_count: { type: integer } - created_link_count: { type: integer } - imported_attachment_count: { type: integer } - documents: { type: array, items: { type: string } } - errors: { type: array, items: { type: string } } - warnings: { type: array, items: { type: string } } - imported_requirement_ids: { type: array, items: { type: integer } } - required: - - success - - message - - imported_count - - created_link_count - - imported_attachment_count - - documents - - errors - - warnings - - imported_requirement_ids - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "413": { $ref: "#/components/responses/ApiError" } - "415": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. - Session/personal token; delegated OAuth denied. Both variants validate uploads and project - permissions. Size/content validation and Rocket multipart failures can return framework bodies. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: { file: { type: string, format: binary } } - required: [ file ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/imports/bundle: - post: - operationId: imports_import_project_bundle - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - project_id: { type: integer } - slug: { type: string } - project_base_path: { type: string } - imported_counts: - type: object - properties: - categories: { type: integer } - applicability: { type: integer } - requirement_statuses: { type: integer } - verification_statuses: { type: integer } - verification_methods: { type: integer } - custom_fields: { type: integer } - requirements: { type: integer } - verifications: { type: integer } - matrix_links: { type: integer } - comments: { type: integer } - members: { type: integer } - reviewers: { type: integer } - attachments: { type: integer } - use_cases: { type: integer } - use_case_links: { type: integer } - required: - - categories - - applicability - - requirement_statuses - - verification_statuses - - verification_methods - - custom_fields - - requirements - - verifications - - matrix_links - - comments - - members - - reviewers - - attachments - - use_cases - - use_case_links - additionalProperties: true - warnings: { type: array, items: { type: string } } - errors: { type: array, items: { type: string } } - required: [ project_id, slug, project_base_path, imported_counts, warnings, errors ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "413": { $ref: "#/components/responses/ApiError" } - "415": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. - Session/personal token; delegated OAuth denied. Both variants validate uploads and project - permissions. Size/content validation and Rocket multipart failures can return framework bodies. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: { file: { type: string, format: binary }, group_id: { type: integer } } - required: [ file ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects - x-marreq-csrf: session-csrf - /api/admin/logs: - get: - operationId: logs_list - parameters: - - { in: query, name: entity_type, schema: { type: string }, required: false } - - { in: query, name: action_type, schema: { type: string }, required: false } - - { in: query, name: since, schema: { type: string }, required: false } - - { in: query, name: until, schema: { type: string }, required: false } - - { in: query, name: entity_id, schema: { type: integer }, required: false } - - { in: query, name: user_id, schema: { type: integer }, required: false } - - { in: query, name: project_id, schema: { type: integer }, required: false } - - { in: query, name: limit, schema: { type: integer }, required: false } - - { in: query, name: offset, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AdminLogList" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both - variants. Datetime filters are parsed server-side; pagination and top are clamped. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - /api/admin/logs/stats: - get: - operationId: logs_stats - parameters: - - { in: query, name: entity_type, schema: { type: string }, required: false } - - { in: query, name: action_type, schema: { type: string }, required: false } - - { in: query, name: since, schema: { type: string }, required: false } - - { in: query, name: until, schema: { type: string }, required: false } - - { in: query, name: user_id, schema: { type: integer }, required: false } - - { in: query, name: project_id, schema: { type: integer }, required: false } - - { in: query, name: top, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/LogStatsResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both - variants. Datetime filters are parsed server-side; pagination and top are clamped. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - /api/admin/logs/export.json: - get: - operationId: logs_export_json - parameters: - - { in: query, name: entity_type, schema: { type: string }, required: false } - - { in: query, name: action_type, schema: { type: string }, required: false } - - { in: query, name: since, schema: { type: string }, required: false } - - { in: query, name: until, schema: { type: string }, required: false } - - { in: query, name: entity_id, schema: { type: integer }, required: false } - - { in: query, name: user_id, schema: { type: integer }, required: false } - - { in: query, name: project_id, schema: { type: integer }, required: false } - - { in: query, name: limit, schema: { type: integer }, required: false } - - { in: query, name: offset, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AdminLogList" } } } - headers: { Content-Disposition: { required: true, schema: { type: string } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both - variants. Datetime filters are parsed server-side; pagination and top are clamped. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - /api/admin/logs/cleanup: - post: - operationId: logs_cleanup - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { deleted: { type: integer } } - required: [ deleted ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both - variants. Datetime filters are parsed server-side; pagination and top are clamped. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { days: { type: integer, minimum: 1 } } - required: [ days ] - additionalProperties: true - x-marreq-authorization: instance-admin - x-marreq-csrf: session-csrf - /api/projects/{project_id}/matrix: - get: - operationId: matrix_list_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/MatrixLink" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every - entity belongs to the project; both variants share the shape. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/verifications/{verification_id}/matrix: - get: - operationId: matrix_get_verification_matrix - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: verification_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { verification_id: { type: integer }, requirement_ids: { type: array, items: { type: integer } } } - required: [ verification_id, requirement_ids ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every - entity belongs to the project; both variants share the shape. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - put: - operationId: matrix_put_verification_matrix - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: verification_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - status: { type: string } - verification_id: { type: integer } - requirement_ids: { type: array, items: { type: integer } } - required: [ status, verification_id, requirement_ids ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every - entity belongs to the project; both variants share the shape. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { requirement_ids: { type: array, items: { type: integer } } } - required: [ requirement_ids ] - additionalProperties: true - x-marreq-bearer-scopes: [ traceability:write ] - x-marreq-authorization: project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/mcp/principal: - get: - operationId: mcp_principal - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: [] - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/McpPrincipalResponse' } } } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - /api/mcp/audit: - post: - operationId: mcp_audit - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user - credential; stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are - enforced. Shared in Server and Cloud. - security: [ { bearerAuth: [] } ] - x-marreq-authentication-note: Personal API tokens only; delegated OAuth and sessions rejected. - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/McpAuditRequest" } } } - } - x-marreq-authorization: personal-api-token-only;project-membership - x-marreq-csrf: session-csrf - /api/mcp/internal/audit: - post: - operationId: mcp_internal_audit - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user - credential; stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are - enforced. Shared in Server and Cloud. - security: [ { bearerAuth: [], mcpServiceAuth: [] }, { cookieAuth: [], mcpServiceAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/McpAuditRequest" } } } - } - x-marreq-authorization: internal-mcp-service-secret;authenticated-user;project-membership - x-marreq-csrf: session-csrf - /api/projects/{project_id}/me/permissions: - get: - operationId: members_get_my_permissions - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/EffectivePermissions" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ projects:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/members: - get: - operationId: members_list_members - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/MemberResponse" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/members/{user_id}: - put: - operationId: members_set_member_role - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: user_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/MemberResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { role: { type: integer, minimum: 1, maximum: 4 } } - required: [ role ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: members_remove_member - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: user_id, required: true, schema: { type: integer } } - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/reviewers: - get: - operationId: members_list_project_reviewers - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ projects:read ] - x-marreq-authorization: project-membership;ViewRequirements - put: - operationId: members_put_project_reviewers - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current - membership. Reviewer writes require project members; the last project admin cannot be removed. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/ProjectReviewersResponse" } } } - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived - x-marreq-csrf: session-csrf - /api/meta/deployment: - get: - operationId: meta_deployment_info - security: [] - parameters: [] - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/DeploymentInfo' } } } - /api/meta/build: - get: - operationId: meta_build_info - security: [] - parameters: [] - responses: - '200': - { - description: Success, - content: { application/json: { schema: { $ref: '#/components/schemas/BuildInfo' } } } - } - /api/notifications: - get: - operationId: notifications_list - parameters: - - { in: query, name: unread_only, schema: { type: boolean }, required: false } - - { in: query, name: limit, schema: { type: integer }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Notification" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - /api/notifications/{id}/read: - patch: - operationId: notifications_mark_read - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { success: { type: boolean } } - required: [ success ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - x-marreq-csrf: session-csrf - /api/notifications/read-all: - post: - operationId: notifications_mark_all_read - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: { type: object, properties: { count: { type: integer } }, required: [ count ], additionalProperties: true } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - x-marreq-csrf: session-csrf - /api/notifications/preferences: - get: - operationId: notifications_get_preferences - parameters: [] - responses: - "200": - description: Success - content: - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/NotificationPreference" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - /api/notifications/preferences/{project_id}: - put: - operationId: notifications_set_preference - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - notify_in_app: { anyOf: [ { type: boolean }, { type: "null" } ] } - notify_email: { anyOf: [ { type: boolean }, { type: "null" } ] } - required: [] - additionalProperties: true - x-marreq-authorization: session-user-owned - x-marreq-csrf: session-csrf - delete: - operationId: notifications_delete_preference - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - x-marreq-csrf: session-csrf - /.well-known/oauth-authorization-server: - get: - operationId: oauth_authorization_server_metadata - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - issuer: &id001 { type: string } - authorization_endpoint: *id001 - token_endpoint: *id001 - registration_endpoint: *id001 - response_types_supported: { type: array, items: *id001 } - grant_types_supported: { type: array, items: *id001 } - code_challenge_methods_supported: { type: array, items: *id001 } - scopes_supported: { type: array, items: *id001 } - token_endpoint_auth_methods_supported: { type: array, items: *id001 } - authorization_response_iss_parameter_supported: { type: boolean } - required: - [ - issuer, - authorization_endpoint, - token_endpoint, - registration_endpoint, - response_types_supported, - grant_types_supported, - code_challenge_methods_supported, - scopes_supported, - token_endpoint_auth_methods_supported, - authorization_response_iss_parameter_supported - ] - additionalProperties: true - description: See implementation and http-api-contract.md for validation rules. - security: [] - /.well-known/oauth-protected-resource/mcp: - get: - operationId: oauth_protected_resource_metadata_mcp - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - resource: *id001 - authorization_servers: { type: array, items: *id001 } - scopes_supported: { type: array, items: *id001 } - bearer_methods_supported: { type: array, items: *id001 } - required: [ resource, authorization_servers, scopes_supported, bearer_methods_supported ] - additionalProperties: true - description: See implementation and http-api-contract.md for validation rules. - security: [] - /oauth/register: - post: - operationId: oauth_register - parameters: [] - responses: - "201": - description: Success - content: - application/json: - schema: - type: object - properties: - client_id: { type: string } - client_name: { type: string } - redirect_uris: { type: array, items: { type: string } } - token_endpoint_auth_method: { type: string, const: none } - required: [ client_id, client_name, redirect_uris, token_endpoint_auth_method ] - additionalProperties: true - "400": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - "401": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "503": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - description: Audited api/oauth.rs RegistrationRequest/register/OAuthProtocolError and auth/delegated.rs. - Public PKCE client registration, shared in Server and Cloud; OAuth errors are not ApiError envelopes. - security: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { client_name: { type: string }, redirect_uris: { type: array, items: { type: string } } } - required: [ client_name, redirect_uris ] - additionalProperties: true - x-marreq-authorization: public-dynamic-client-registration;redirect-uri-policy;registration-rate-limit - /oauth/authorize: - get: - operationId: oauth_authorize_page - parameters: - - { in: query, name: response_type, schema: { type: string }, required: true } - - { in: query, name: client_id, schema: { type: string }, required: true } - - { in: query, name: redirect_uri, schema: { type: string }, required: true } - - { in: query, name: scope, schema: { type: string }, required: true } - - { in: query, name: state, schema: { type: string }, required: true } - - { in: query, name: code_challenge, schema: { type: string }, required: true } - - { in: query, name: code_challenge_method, schema: { type: string }, required: true } - - { in: query, name: resource, schema: { type: string }, required: true } - responses: - "200": - description: HTML consent form for an authenticated browser. - content: { text/html: { schema: { type: string } } } - "303": - description: 303 redirect to login or the validated OAuth redirect_uri. - headers: { Location: { required: true, schema: { type: string } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/oauth.rs authorize_page/checked_request. Trusted redirect errors and anonymous login - return 303; consent returns HTML. Unknown clients and invalid untrusted requests return API errors. - Same in both variants. - security: [] - x-marreq-authorization: validated-client-redirect-resource-pkce-scopes;optional-session-consent - post: - operationId: oauth_authorize_decision - parameters: [] - responses: - "303": - description: 303 redirect to login or the validated OAuth redirect_uri. - headers: { Location: { required: true, schema: { type: string } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/oauth.rs ConsentForm and authorize_decision. Form submissions use the session and - origin/CSRF fairing policy; both allow and deny redirect with OAuth state/issuer. Shared in both - variants. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - client_id: { type: string } - redirect_uri: { type: string } - scope: { type: string } - state: { type: string } - code_challenge: { type: string } - resource: { type: string } - decision: { type: string } - required: [ client_id, redirect_uri, scope, state, code_challenge, resource, decision ] - additionalProperties: true - x-marreq-authorization: session;validated-client-redirect-resource-pkce-scopes;consent-decision - x-marreq-csrf: auth-origin-fallback - /oauth/token: - post: - operationId: oauth_token - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - access_token: { type: string } - token_type: { type: string, const: Bearer } - expires_in: { type: integer } - refresh_token: { type: string } - scope: { type: string } - required: [ access_token, token_type, expires_in, refresh_token, scope ] - additionalProperties: true - "400": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - "401": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "503": - description: OAuth protocol error. - content: - application/json: - schema: { type: object, properties: { error: { type: string } }, required: [ error ], additionalProperties: true } - description: Audited api/oauth.rs TokenForm/token and auth/delegated.rs TokenResponse. Public form endpoint - with client/resource/code/refresh checks; protocol errors use an error string. Shared in both - variants. - security: [] - requestBody: - required: true - content: { application/x-www-form-urlencoded: { schema: { $ref: "#/components/schemas/OAuthTokenRequest" } } } - x-marreq-authorization: public-pkce-code-or-refresh-token;client-resource-binding;single-use-rotation - /api/projects: - post: - operationId: projects_create - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - id: { type: integer } - name: { type: string } - slug: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_base_path: { type: string } - status: { type: string, const: ok } - required: [ id, name, slug, description, group_id, project_base_path, status ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group - permissions, ownership, archive status and strict PATCH field handling apply. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ name ] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects - x-marreq-csrf: session-csrf - get: - operationId: projects_session_list_for_session - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Project" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects_session.rs and models::Project. Admin all-project view, otherwise current - memberships; bearer project scope narrows the list. Shared in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ projects:read ] - x-marreq-authorization: current-project-membership;token-project-scope - /api/projects/{project_id}: - patch: - operationId: projects_update - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - id: { type: integer } - name: { type: string } - slug: { type: string } - project_base_path: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - creation_date: { anyOf: [ { type: string }, { type: "null" } ] } - update_date: { anyOf: [ { type: string }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - status: { $ref: "#/components/schemas/ProjectStatus" } - required: - - id - - name - - slug - - project_base_path - - description - - creation_date - - update_date - - owner_id - - group_id - - status - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group - permissions, ownership, archive status and strict PATCH field handling apply. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: { anyOf: [ { type: string }, { type: "null" } ] } - description: { anyOf: [ { type: string }, { type: "null" } ] } - status: { anyOf: [ { $ref: "#/components/schemas/ProjectStatus" }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [] - additionalProperties: false - x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjectConfiguration;group-ManageProjects;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: projects_delete - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group - permissions, ownership, archive status and strict PATCH field handling apply. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { confirm_slug: { type: string } } - required: [ confirm_slug ] - additionalProperties: false - x-marreq-authorization: session;project-owner-or-instance-admin;confirm-slug - x-marreq-csrf: session-csrf - /api/projects/{project_id}/archive: - post: - operationId: projects_archive - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Project" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group - permissions, ownership, archive status and strict PATCH field handling apply. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;project-owner-or-instance-admin - x-marreq-csrf: session-csrf - /api/projects/{project_id}/unarchive: - post: - operationId: projects_unarchive - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": { description: Success, content: { application/json: { schema: { $ref: "#/components/schemas/Project" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group - permissions, ownership, archive status and strict PATCH field handling apply. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;project-owner-or-instance-admin - x-marreq-csrf: session-csrf - /api/project-from-path/{slug}: - get: - operationId: projects_session_project_from_path - parameters: [ { in: path, name: slug, required: true, schema: { type: string } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - id: { type: integer } - name: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - slug: { type: string } - route_slug: { type: string } - required: [ id, name, description, slug, route_slug ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/projects_session.rs project_from_path. Session-only slug lookup with membership - checks, shared in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;project-membership-or-instance-admin - /api/projects/{project_id}/reports/types: - get: - operationId: reports_list_types - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: array - items: - type: object - properties: - key: { type: string } - title: { type: string } - description: { type: string } - formats: { type: array, items: { type: string, enum: [ pdf, odt ] } } - sections: { type: array, items: { $ref: "#/components/schemas/ReportSectionSpec" } } - default_definition: { $ref: "#/components/schemas/ReportDefinition" } - required: [ key, title, description, formats, sections, default_definition ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements - /api/projects/{project_id}/report_templates: - get: - operationId: reports_list_templates - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/ReportTemplateView" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template - post: - operationId: reports_create_template - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "201": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateInput" } } } - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin - x-marreq-csrf: session-csrf - /api/projects/{project_id}/report_templates/{template_id}: - patch: - operationId: reports_update_template - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: template_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/ReportTemplateInput" } } } - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin - x-marreq-csrf: session-csrf - delete: - operationId: reports_delete_template - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: template_id, required: true, schema: { type: integer } } - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin - x-marreq-csrf: session-csrf - /api/projects/{project_id}/reports/{file}: - post: - operationId: reports_generate - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: file, required: true, schema: { type: string } } - responses: - "200": - description: File download; Content-Disposition supplies the filename. - headers: { Content-Disposition: { required: true, schema: { type: string } } } - content: - application/pdf: { schema: { type: string, format: binary } } - application/vnd.oasis.opendocument.text: { schema: { type: string, format: binary } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; - delegated OAuth denied. Both variants enforce project view permission, private-template visibility, - owner/admin mutation rules and archived-project write denial. Definition defaults and optional section - options follow Serde. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: false - content: - application/json: - schema: - type: object - properties: - template_id: { anyOf: [ { type: integer }, { type: "null" } ] } - definition: { anyOf: [ { $ref: "#/components/schemas/ReportDefinitionInput" }, { type: "null" } ] } - required: [] - additionalProperties: true - x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: - get: - operationId: requirement_diff_diff_versions_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: req_id, required: true, schema: { type: integer } } - - { in: path, name: v1, required: true, schema: { type: integer } } - - { in: path, name: v2, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementDiff" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both - versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label - fields are omitted, not null. Shared wire representation in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: - get: - operationId: requirement_diff_diff_baseline_vs_current - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - - { in: path, name: req_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementDiff" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both - versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label - fields are omitted, not null. Shared wire representation in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read, baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirement-version-links: - post: - operationId: requirement_version_links_create - parameters: - [ - { in: path, name: project_id, required: true, schema: { type: integer } }, - { - in: header, - name: Idempotency-Key, - required: false, - schema: { type: string }, - description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore - or hyphen. Reuse with a different payload returns 409." - } - ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionLink" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound - source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches - return 409. Both variants share this contract. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - source_version_id: { type: integer } - target_version_id: { type: integer } - link_type: { type: string } - rationale: { anyOf: [ { type: string }, { type: "null" } ] } - metadata: { anyOf: [ {}, { type: "null" } ] } - required: [ source_version_id, target_version_id, link_type ] - additionalProperties: true - x-marreq-bearer-scopes: [ traceability:write ] - x-marreq-authorization: project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - get: - operationId: requirement_version_links_list - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: source_version_id, schema: { type: integer }, required: false } - - { in: query, name: target_version_id, schema: { type: integer }, required: false } - - { in: query, name: link_type, schema: { type: string }, required: false } - responses: - "200": - description: Success - content: - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementVersionLink" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound - source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches - return 409. Both variants share this contract. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirement-version-links/{link_id}: - delete: - operationId: requirement_version_links_delete - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: link_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionLink" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound - source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches - return 409. Both variants share this contract. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:write ] - x-marreq-authorization: project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirement-version-links/link-types: - get: - operationId: requirement_version_links_link_types - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { link_types: { type: array, items: { type: string } } } - required: [ link_types ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirement_version_links.rs, models and RequirementService. Project-bound - source/target versions; optional Idempotency-Key is supported on create, reused payload mismatches - return 409. Both variants share this contract. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:read ] - x-marreq-authorization: project-membership;project-access - /api/projects/{project_id}/requirements: - get: - operationId: requirements_list_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: approval_state, schema: { type: string }, required: false } - - { in: query, name: q, schema: { type: string }, required: false } - - { in: query, name: sort_column, schema: { type: string }, required: false } - - { in: query, name: sort_dir, schema: { type: string }, required: false } - - { in: query, name: status_id, schema: { type: integer }, required: false } - - { in: query, name: category_id, schema: { type: integer }, required: false } - - { in: query, name: view_id, schema: { type: integer }, required: false } - - { in: query, name: has_tests, schema: { type: boolean }, required: false } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementListRow" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - post: - operationId: requirements_create_by_project - parameters: - [ - { in: path, name: project_id, required: true, schema: { type: integer } }, - { - in: header, - name: Idempotency-Key, - required: false, - schema: { type: string }, - description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore - or hyphen. Reuse with a different payload returns 409." - } - ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCreateRequest" } } } - x-marreq-bearer-scopes: [ requirements:write ] - x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/requirements/{id}: - delete: - operationId: requirements_delete - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{id}: - get: - operationId: requirements_get_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementWithTraceSummary" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - patch: - operationId: requirements_patch_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { success: { type: boolean }, message: { type: string } } - required: [ success, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementPatch" } } } - } - x-marreq-bearer-scopes: [ requirements:write ] - x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{id}/versions: - get: - operationId: requirements_list_versions_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementVersion" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: - get: - operationId: requirements_get_version_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: req_id, required: true, schema: { type: integer } } - - { in: path, name: version_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersionDetail" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirements/{id}/impacted_tests: - get: - operationId: requirements_get_impacted_tests_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read, verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: - put: - operationId: requirements_set_version_approval_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: req_id, required: true, schema: { type: integer } } - - { in: path, name: version_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementVersion" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: { type: object, properties: { state: { type: string } }, required: [ state ], additionalProperties: true } - x-marreq-bearer-scopes: [ requirements:approve ] - x-marreq-authorization: project-membership;project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/saved_views: - get: - operationId: saved_views_list - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/SavedView" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as - serde_json::Value after service validation. Private visibility, ownership, baseline locking and - archived-project rules are enforced in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements;scoped-requirements-read - post: - operationId: saved_views_create - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - { - description: Success, - content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as - serde_json::Value after service validation. Private visibility, ownership, baseline locking and - archived-project rules are enforced in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/SavedViewPayload" } } } - } - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked - x-marreq-csrf: session-csrf - /api/projects/{project_id}/saved_views/{view_id}: - get: - operationId: saved_views_get - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: view_id, required: true, schema: { type: integer } } - responses: - "200": - { - description: Success, - content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as - serde_json::Value after service validation. Private visibility, ownership, baseline locking and - archived-project rules are enforced in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;visible-owner-or-shared - patch: - operationId: saved_views_update - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: view_id, required: true, schema: { type: integer } } - responses: - "200": - { - description: Success, - content: { application/json: { schema: { $ref: "#/components/schemas/SavedView" } } } - } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as - serde_json::Value after service validation. Private visibility, ownership, baseline locking and - archived-project rules are enforced in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/SavedViewPayload" } } } - } - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked - x-marreq-csrf: session-csrf - delete: - operationId: saved_views_delete - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: view_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as - serde_json::Value after service validation. Private visibility, ownership, baseline locking and - archived-project rules are enforced in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/semantic_search: - get: - operationId: semantic_search_semantic_search - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: q, schema: { type: string }, required: true } - - { in: query, name: k, schema: { type: integer, minimum: 0 }, required: false } - - { in: query, name: status_filter, schema: { type: integer }, required: false } - - { in: query, name: category_filter, schema: { type: integer }, required: false } - - { in: query, name: applicability_filter, schema: { type: integer }, required: false } - - { in: query, name: verification_filter, schema: { type: integer }, required: false } - - { in: query, name: debug, schema: { type: boolean }, required: false } - responses: - "200": - description: Success - content: - application/json: - schema: - oneOf: - - type: object - properties: - enabled: { type: boolean, const: false } - message: { type: string } - results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" }, maxItems: 0 } - total: { type: integer, const: 0 } - required: [ enabled, message, results, total ] - additionalProperties: true - - type: object - properties: - enabled: { type: boolean, const: true } - results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" } } - query: { type: string } - total: { type: integer } - timing_ms: { type: integer } - required: [ enabled, results, query, total ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/semantic_search.rs and models/semantic_search.rs. Disabled and enabled payloads - differ explicitly; timing_ms and rank details are optional. Both variants use runtime embedding - configuration. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership - /api/projects/{project_id}/requirements/ask: - post: - operationId: semantic_search_ask - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RagAnswerResponse" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/semantic_search.rs ask and RagAnswerResponse. RAG-disabled state returns 400, - provider/repository failures map to API errors. Shared in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - query: { type: string } - k: { anyOf: [ { type: integer, minimum: 0 }, { type: "null" } ] } - status_filter: { anyOf: [ { type: integer }, { type: "null" } ] } - category_filter: { anyOf: [ { type: integer }, { type: "null" } ] } - applicability_filter: { anyOf: [ { type: integer }, { type: "null" } ] } - verification_filter: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ query ] - additionalProperties: true - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/index_status: - get: - operationId: semantic_search_index_status - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: { default: { description: Wire behavior has not yet been verified. } } - x-marreq-unverified: true - description: Wire contract pending audit; required consumers fail verification. - security: [ { cookieAuth: [] } ] - /api/status: - get: - operationId: status_list_requirement_statuses - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RequirementStatus" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; - reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ViewRequirements - post: - operationId: status_create_requirement_status - parameters: [] - responses: - "201": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; - reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewRequirementStatus" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/status/{id}: - put: - operationId: status_update_requirement_status - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; - reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewRequirementStatus" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: status_delete_requirement_status - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; - reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status - mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{id}/trace_up: - get: - operationId: traceability_trace_up - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - parent: { anyOf: [ { $ref: "#/components/schemas/Requirement" }, { type: "null" } ] } - parents: { type: array, items: { $ref: "#/components/schemas/Requirement" } } - required: [ parent, parents ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses - retain nullable first parent, complete parent arrays and explicit no_change result, identically in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read, traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirements/{id}/trace_down: - get: - operationId: traceability_trace_down - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - child_requirements: { type: array, items: { $ref: "#/components/schemas/Requirement" } } - linked_tests: { type: array, items: { $ref: "#/components/schemas/Verification" } } - required: [ child_requirements, linked_tests ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses - retain nullable first parent, complete parent arrays and explicit no_change result, identically in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read, verifications:read, traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/coverage_report: - get: - operationId: traceability_coverage_report - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - requirements_without_tests: { type: array, items: { type: integer } } - tests_without_requirements: { type: array, items: { type: integer } } - suspect_links: - type: array - items: - type: object - properties: { req_id: { type: integer }, verification_id: { type: integer } } - required: [ req_id, verification_id ] - additionalProperties: true - required: [ requirements_without_tests, tests_without_requirements, suspect_links ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses - retain nullable first parent, complete parent arrays and explicit no_change result, identically in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ traceability:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/traceability/clear_suspect: - post: - operationId: traceability_clear_suspect - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, enum: [ ok, no_change ] }, cleared: { type: boolean } } - required: [ status, cleared ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses - retain nullable first parent, complete parent arrays and explicit no_change result, identically in - both variants. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { req_id: { type: integer }, verification_id: { type: integer } } - required: [ req_id, verification_id ] - additionalProperties: true - x-marreq-authorization: EditRequirements;project-membership;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/traceability/clear_suspect: - post: - operationId: traceability_clear_suspect_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, enum: [ ok, no_change ] }, cleared: { type: boolean } } - required: [ status, cleared ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses - retain nullable first parent, complete parent arrays and explicit no_change result, identically in - both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { req_id: { type: integer }, verification_id: { type: integer } } - required: [ req_id, verification_id ] - additionalProperties: true - x-marreq-bearer-scopes: [ traceability:write ] - x-marreq-authorization: EditRequirements;project-membership;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/use-cases: - get: - operationId: use_cases_list - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: query, name: q, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } - - in: query - name: approval_state - required: false - schema: { anyOf: [ { type: string }, { type: 'null' } ] } - - { in: query, name: actor, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } - - { - in: query, - name: has_suspect, - required: false, - schema: { anyOf: [ { type: boolean }, { type: 'null' } ] } - } - - in: query - name: requirement_id - required: false - schema: { anyOf: [ { type: integer }, { type: 'null' } ] } - - in: query - name: verification_id - required: false - schema: { anyOf: [ { type: integer }, { type: 'null' } ] } - - { - in: query, - name: sort_column, - required: false, - schema: { anyOf: [ { type: string }, { type: 'null' } ] } - } - - { in: query, name: sort_dir, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRow_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - post: - operationId: use_cases_create - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - in: header - name: Idempotency-Key - required: false - schema: &id002 { type: string } - description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. - responses: - '201': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseDetail_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - description: Create the shared draft; optional reference_code, title, primary_actor and document. A missing - code is generated. Supports Idempotency-Key. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:write' ] - x-marreq-csrf: session-csrf - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - reference_code: { type: [ string, 'null' ] } - title: { type: [ string, 'null' ] } - primary_actor: { type: [ string, 'null' ] } - document: { anyOf: [ { $ref: '#/components/schemas/UseCaseDocument' }, { type: 'null' } ] } - additionalProperties: true - /api/projects/{project_id}/use-cases/next-code: - get: - operationId: use_cases_next_code - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_NextCode_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}: - get: - operationId: use_cases_get - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { - in: query, - name: version_id, - required: false, - schema: { anyOf: [ { type: integer }, { type: 'null' } ] } - } - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseDetail_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - delete: - operationId: use_cases_delete - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': { description: Deleted } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: [ 'use_cases:write' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/draft: - put: - operationId: use_cases_put_draft - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftChangeView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - requestBody: - required: true - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_PutDraftRequest_request' } } } - x-marreq-bearer-scopes: [ 'use_cases:write' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - delete: - operationId: use_cases_discard_draft - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': { description: Deleted } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: [ 'use_cases:write' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - get: - operationId: use_cases_get_draft - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/draft/operations: - post: - operationId: use_cases_draft_operations - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_DraftChangeView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:write' ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - base_revision: { anyOf: [ &id003 { type: integer }, { type: 'null' } ] } - operations: { type: array, items: { $ref: '#/components/schemas/UseCaseOperation' } } - additionalProperties: true - required: [ operations ] - x-marreq-csrf: session-csrf - /api/projects/{project_id}/use-cases/{id}/draft/publish: - post: - operationId: use_cases_publish - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - in: header - name: Idempotency-Key - required: false - schema: *id002 - description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. - responses: - '201': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/UseCasePublishResponse' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:write' ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { base_revision: *id003, change_note: { anyOf: [ *id002, { type: 'null' } ] } } - additionalProperties: true - required: [ base_revision ] - x-marreq-csrf: session-csrf - /api/projects/{project_id}/use-cases/{id}/versions: - get: - operationId: use_cases_list_versions - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_VersionSummary_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: - put: - operationId: use_cases_set_version_approval - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: path, name: version_id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_VersionView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - requestBody: - required: true - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_SetApprovalRequest_request' } } } - x-marreq-bearer-scopes: [ 'use_cases:approve' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/links: - get: - operationId: use_cases_list_links - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_LinkView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - post: - operationId: use_cases_create_link - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - '201': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_LinkView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:write' ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - element_id: { anyOf: [ *id002, { type: 'null' } ] } - requirement_id: { anyOf: [ *id003, { type: 'null' } ] } - verification_id: { anyOf: [ *id003, { type: 'null' } ] } - rationale: { anyOf: [ *id002, { type: 'null' } ] } - additionalProperties: true - x-marreq-csrf: session-csrf - /api/projects/{project_id}/use-cases/{id}/links/{link_id}: - delete: - operationId: use_cases_delete_link - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: path, name: link_id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '204': { description: Deleted } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: [ 'use_cases:write' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: - post: - operationId: use_cases_clear_link_suspect - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: path, name: link_id, required: true, schema: { type: integer } } - - in: header - name: X-CSRF-Token - required: false - schema: { type: string } - description: Required for session mutations; Bearer requests use scoped authentication instead. - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_ClearSuspectResponse_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-csrf: session-csrf - x-marreq-bearer-scopes: [ 'use_cases:write' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/requirements/{requirement_id}/use-cases: - get: - operationId: use_cases_requirement_use_cases - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRef_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/verifications/{verification_id}/use-cases: - get: - operationId: use_cases_verification_use_cases - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: verification_id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseRef_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/projects/{project_id}/use-cases/{id}/draft/diff: - get: - operationId: use_cases_draft_diff - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: query, name: include_unchanged, required: false, schema: &id004 { type: boolean } } - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/UseCaseDiff' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:read' ] - /api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: - get: - operationId: use_cases_version_diff - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: path, name: a, required: true, schema: { type: integer } } - - { in: path, name: b, required: true, schema: { type: integer } } - - { in: query, name: include_unchanged, required: false, schema: *id004 } - responses: - '200': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/UseCaseDiff' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:read' ] - /api/projects/{project_id}/use-cases/{id}/comments: - get: - operationId: use_cases_list_comments - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { - in: query, - name: version_id, - required: false, - schema: { anyOf: [ { type: integer }, { type: 'null' } ] } - } - - { in: query, name: element_id, required: false, schema: { anyOf: [ { type: string }, { type: 'null' } ] } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_UseCaseCommentView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - post: - operationId: use_cases_create_comment - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - in: header - name: Idempotency-Key - required: false - schema: *id002 - description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. - responses: - '201': - description: Success - content: { application/json: { schema: { $ref: '#/components/schemas/use_cases_UseCaseCommentView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '403': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': { description: Guard/fairing/validation failure; body may be empty or framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Use-case service contract; see use-cases-api.md for revision conflicts, document validation, - idempotency, publishing, links and review permissions. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ 'use_cases:write' ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - body: *id002 - use_case_version_id: { anyOf: [ *id003, { type: 'null' } ] } - element_id: { anyOf: [ *id002, { type: 'null' } ] } - additionalProperties: true - required: [ body ] - x-marreq-csrf: session-csrf - /api/projects/{project_id}/baselines/{baseline_id}/use-cases: - get: - operationId: use_cases_baseline_use_cases - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - responses: - '200': - description: Success - content: - application/json: { schema: { type: array, items: { $ref: '#/components/schemas/use_cases_BaselineUseCaseView_response' } } } - '400': { $ref: '#/components/responses/ApiError' } - '401': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '403': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '404': { $ref: '#/components/responses/ApiError' } - '409': { $ref: '#/components/responses/ApiError' } - '422': - { - description: 'Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework - generated; do not require an ApiError body.' - } - '500': { $ref: '#/components/responses/ApiError' } - x-marreq-bearer-scopes: [ 'use_cases:read' ] - description: Session or scoped Bearer authentication; project membership and - ViewRequirements/EditRequirements/review permissions apply. Archived projects reject mutations. See - use-cases-api.md for validation and revision-conflict semantics. - /api/users/{id}: - put: - operationId: users_update - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/entities_User_response" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud - returns 410 for user creation and restricts promotion/deletion of the designated site administrator. - security: [ { cookieAuth: [] } ] - requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UpdateUser" } } } } - x-marreq-authorization: instance-admin;deployment-admin-promotion-policy - x-marreq-csrf: session-csrf - delete: - operationId: users_delete - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud - returns 410 for user creation and restricts promotion/deletion of the designated site administrator. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin;deployment-admin-deletion-policy - x-marreq-csrf: session-csrf - /api/users/{id}/password: - put: - operationId: users_set_password - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud - returns 410 for user creation and restricts promotion/deletion of the designated site administrator. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { new_password: { type: string }, confirm_password: { type: string } } - required: [ new_password, confirm_password ] - additionalProperties: true - x-marreq-authorization: instance-admin - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verifications/{verification_id}/control: - get: - operationId: verification_control_get_verification_control - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: verification_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs - are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, - trimming/length validation and reviewer rules. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - put: - operationId: verification_control_put_verification_control - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: verification_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs - are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, - trimming/length validation and reviewer rules. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/VerificationControlInput" } } } - x-marreq-bearer-scopes: [ verifications:write ] - x-marreq-authorization: project-membership;EditRequirements;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/requirements/{requirement_id}/close_out: - get: - operationId: verification_control_get_requirement_close_out - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCloseOutView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs - are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, - trimming/length validation and reviewer rules. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/close_out: - get: - operationId: verification_control_get_project_close_out - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: { schema: { type: object, additionalProperties: { $ref: "#/components/schemas/RequirementCloseOutView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs - are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, - trimming/length validation and reviewer rules. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ requirements:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/requirements/{requirement_id}/compliance: - put: - operationId: verification_control_put_requirement_compliance - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: requirement_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/RequirementCloseOutView" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs - are cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, - trimming/length validation and reviewer rules. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - compliance: { anyOf: [ { type: string }, { type: "null" } ] } - note: { anyOf: [ { type: string }, { type: "null" } ] } - required: [] - additionalProperties: false - x-marreq-bearer-scopes: [ requirements:write ] - x-marreq-authorization: project-membership;EditRequirements;project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verifications/{id}/snapshots: - get: - operationId: verification_diff_list_snapshots_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: { type: integer } - created_at: { type: string } - name: { type: string } - reference_code: { type: string } - status_id: { type: integer } - required: [ id, created_at, name, reference_code, status_id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both - versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label - fields are omitted, not null. Shared wire representation in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: - get: - operationId: verification_diff_diff_snapshots_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - - { in: path, name: v1, required: true, schema: { type: integer } } - - { in: path, name: v2, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/VerificationVersionDiff" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both - versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label - fields are omitted, not null. Shared wire representation in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: - get: - operationId: verification_diff_diff_baseline_vs_current - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: baseline_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/VerificationVersionDiff" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both - versions/snapshots must belong to the requested entity and project. Optional SingleValueDiff/label - fields are omitted, not null. Shared wire representation in both variants. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read, baselines:read ] - x-marreq-authorization: project-membership;ViewRequirements - /api/projects/{project_id}/verification-methods: - get: - operationId: verification_methods_list_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/VerificationMethod" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: ViewRequirements - post: - operationId: verification_methods_create_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationMethod" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verification-methods/{method_id}: - put: - operationId: verification_methods_update_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: method_id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationMethod" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: verification_methods_delete_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: method_id, required: true, schema: { type: integer } } - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/verification-status: - get: - operationId: verification_status_list_verification_statuses - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/VerificationStatus" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ViewRequirements - post: - operationId: verification_status_create_verification_status - parameters: [] - responses: - "201": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationStatus" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/verification-status/{id}: - put: - operationId: verification_status_update_verification_status - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerificationStatus" } } } - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - delete: - operationId: verification_status_delete_verification_status - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud - wire shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System - status mutations are rejected. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: ManageProjectConfiguration;not-archived - x-marreq-csrf: session-csrf - /api/verifications: - get: - operationId: verifications_list - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;ViewRequirements - post: - operationId: verifications_create - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } - } - x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verifications: - get: - operationId: verifications_list_by_project - parameters: [ { in: path, name: project_id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Verification" } } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - post: - operationId: verifications_create_by_project - parameters: - [ - { in: path, name: project_id, required: true, schema: { type: integer } }, - { - in: header, - name: Idempotency-Key, - required: false, - schema: { type: string }, - description: "Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore - or hyphen. Reuse with a different payload returns 409." - } - ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } - } - x-marreq-bearer-scopes: [ verifications:write ] - x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verifications/{id}: - get: - operationId: verifications_get_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-bearer-scopes: [ verifications:read ] - x-marreq-authorization: project-membership;ViewRequirements - put: - operationId: verifications_update_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/NewVerification" } } } - } - x-marreq-bearer-scopes: [ verifications:write ] - x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/verifications/{id}: - get: - operationId: verifications_get - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;ViewRequirements - delete: - operationId: verifications_delete - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Success; empty body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/projects/{project_id}/verifications/{id}/field: - post: - operationId: verifications_update_field_by_project - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: id, required: true, schema: { type: integer } } - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { success: { type: boolean }, message: { type: string } } - required: [ success, message ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants - use the same serializers. Scoped routes check project boundaries and scopes; mutations require - EditRequirements and unarchived projects, with project-reviewer checks for status/approval - transitions. Idempotent creates reject conflicting payloads with 409. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { field: { type: string }, value: { type: string } } - required: [ field, value ] - additionalProperties: true - x-marreq-bearer-scopes: [ verifications:write ] - x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived - x-marreq-csrf: session-csrf - /api/auth/csrf: - get: - operationId: auth_auth_csrf - security: [] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: { csrf_token: { type: string } } - required: [ csrf_token ] - additionalProperties: true - /api/auth/identities/{identity_id}: - delete: - operationId: auth_auth_identity_delete - parameters: [ { in: path, name: identity_id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Owned identity removed; no body. } - "400": { $ref: "#/components/responses/ApiError" } - "401": { $ref: "#/components/responses/ApiError" } - "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [ { cookieAuth: [] } ] - x-marreq-csrf: session-csrf - /api/auth/register: - post: - operationId: auth_public_auth_register - x-marreq-variants: [ cloud ] - parameters: [] - responses: - '201': - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - '200': - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - '400': { $ref: '#/components/responses/ApiError' } - '500': { $ref: '#/components/responses/ApiError' } - description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing - email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. - security: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - username: &id005 { type: string } - name: *id005 - email: *id005 - password: *id005 - required: [ username, name, email, password ] - additionalProperties: true - /api/auth/login: - post: - operationId: auth_auth_login - parameters: [] - responses: - '200': - description: Authenticated session established - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, user: { $ref: '#/components/schemas/entities_User_response' } } - required: [ status, user ] - '400': { $ref: '#/components/responses/ApiError' } - '403': { description: CSRF failure; body may be framework generated } - '410': { $ref: '#/components/responses/ApiError' } - '422': { description: Rocket JSON validation failure } - '500': { $ref: '#/components/responses/ApiError' } - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: [] - x-marreq-csrf: auth-origin-fallback - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [ username, password ] - properties: { username: { type: string }, password: { type: string } } - /api/users: - post: - operationId: users_create - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok }, id: { type: integer } } - required: [ status, id ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "409": { $ref: "#/components/responses/ApiError" } - "410": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud - returns 410 for user creation and restricts promotion/deletion of the designated site administrator. - security: [ { cookieAuth: [] } ] - requestBody: - { - required: true, - content: { application/json: { schema: { $ref: "#/components/schemas/UserCreateRequest" } } } - } - x-marreq-authorization: instance-admin;allows_self_administered_user_creation - x-marreq-csrf: session-csrf - get: - operationId: users_list - parameters: [] - responses: - "200": - description: Success - content: - application/json: { schema: { type: array, items: { $ref: "#/components/schemas/entities_User_response" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud - returns 410 for user creation and restricts promotion/deletion of the designated site administrator. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: instance-admin - /api/projects/{project_id}/attachments/{attachment_id}: - delete: - operationId: attachments_delete - parameters: - - { in: path, name: project_id, required: true, schema: { type: integer } } - - { in: path, name: attachment_id, required: true, schema: { type: integer } } - responses: - "204": { description: Attachment deleted; empty body. Baseline retention can keep the stored bytes. } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "403": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - "503": - description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or - framework-generated. - description: Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same - routes and wire types in Server and Cloud; configured storage limits remain instance policy. - security: [ { cookieAuth: [] }, { bearerAuth: [] } ] - x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied - x-marreq-csrf: session-csrf - /api/auth/external/{provider}/link: - post: - operationId: auth_auth_external_link_start - parameters: [ { in: path, name: provider, required: true, schema: { type: string } } ] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: { authorization_url: { type: string } } - required: [ authorization_url ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": { $ref: "#/components/responses/ApiError" } - "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "404": { $ref: "#/components/responses/ApiError" } - "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: - application/json: - schema: - description: Any JSON value is accepted; an object return_to string overrides the default /settings/account. - properties: { return_to: { type: string } } - x-marreq-csrf: session-csrf - /api/auth/verify-email: - get: - operationId: auth_public_auth_verify_email - x-marreq-variants: [ cloud ] - parameters: [ { in: query, name: token, required: true, schema: *id005 } ] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - '400': { $ref: '#/components/responses/ApiError' } - '500': { $ref: '#/components/responses/ApiError' } - description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing - email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. - security: [] - /api/auth/logout: - post: - operationId: auth_auth_logout - parameters: [] - responses: - '200': - description: Session and CSRF cookies cleared - content: - application/json: { schema: { type: object, properties: { status: { type: string, const: ok } }, required: [ status ] } } - '403': { description: CSRF failure; body may be framework generated } - '500': { $ref: '#/components/responses/ApiError' } - description: Route inventory only. Request/response serialization, status codes and authorization require - audit before compatibility can be established. - security: [] - x-marreq-csrf: auth-origin-fallback - requestBody: - required: false - description: Optional ignored body; the SPA sends an empty JSON object. - content: { application/json: { schema: {} } } - /api/auth/forgot-password: - post: - operationId: auth_public_auth_forgot_password - x-marreq-variants: [ cloud ] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - '400': { $ref: '#/components/responses/ApiError' } - '500': { $ref: '#/components/responses/ApiError' } - description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing - email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. - security: [] - requestBody: - required: true - content: - application/json: - schema: { type: object, properties: { email: *id005 }, required: [ email ], additionalProperties: true } - /api/notifications/unread-count: - get: - operationId: notifications_unread_count - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: { type: object, properties: { count: { type: integer } }, required: [ count ], additionalProperties: true } - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped - to the current user in both variants. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-owned - /api/auth/change-password: - post: - operationId: auth_auth_change_password - parameters: [] - responses: - "200": - description: Success - content: { application/json: { schema: { $ref: "#/components/schemas/AuthStatus" } } } - "400": { $ref: "#/components/responses/ApiError" } - "401": { $ref: "#/components/responses/ApiError" } - "403": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "422": { description: Authentication or CSRF failure; body may be framework-generated or empty. } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in - Server and Cloud. - security: [ { cookieAuth: [] } ] - requestBody: - required: true - content: { application/json: { schema: { $ref: "#/components/schemas/AuthChangePassword" } } } - x-marreq-csrf: session-csrf - /api/oauth/grants: - get: - operationId: oauth_grants - parameters: [] - responses: - "200": - description: Connected applications - content: - application/json: - schema: - type: object - required: [ grants ] - properties: - grants: - type: array - items: - type: object - required: [ id, application_name, scopes, created_at, last_used_at ] - properties: - id: { type: integer } - application_name: { type: string } - scopes: { type: array, items: { type: string } } - created_at: { type: string } - last_used_at: { type: [ string, "null" ] } - additionalProperties: true - additionalProperties: true - "401": { description: Missing or expired session; guard response may be empty. } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants - only, identical in Server and Cloud; no Bearer authentication. - security: [ { cookieAuth: [] } ] - /api/oauth/grants/{id}: - delete: - operationId: oauth_revoke_grant - parameters: [ { in: path, name: id, required: true, schema: { type: integer } } ] - responses: - "204": { description: Owned grant revoked; empty body. } - "401": { description: Missing or expired session; guard response may be empty. } - "403": { description: CSRF failure; body may be framework-generated. } - "404": { $ref: "#/components/responses/ApiError" } - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants - only, identical in Server and Cloud; no Bearer authentication. - security: [ { cookieAuth: [] } ] - x-marreq-csrf: session-csrf - /api/dashboard: - get: - operationId: dashboard_dashboard_json - parameters: [] - responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - user: { $ref: "#/components/schemas/entities_User_response" } - projects: { type: array, items: { $ref: "#/components/schemas/DashboardProject" } } - projects_count: { type: integer } - selected_project_id: { anyOf: [ { type: integer }, { type: "null" } ] } - selected_project_slug: { anyOf: [ { type: string }, { type: "null" } ] } - csrf_token: { type: string } - required: [ user, projects, projects_count, selected_project_id, selected_project_slug, csrf_token ] - additionalProperties: true - "400": { $ref: "#/components/responses/ApiError" } - "401": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "403": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "404": { $ref: "#/components/responses/ApiError" } - "409": { $ref: "#/components/responses/ApiError" } - "422": - description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or - framework-generated. - "500": { $ref: "#/components/responses/ApiError" } - description: Audited api/dashboard.rs and routes/helpers.rs decorate_projects_for_listing. Project cards - include the exact decoration keys; nulls reflect absent selection/metadata. Both variants share this - session payload. - security: [ { cookieAuth: [] } ] - x-marreq-authorization: session-user-project-memberships - /api/auth/reset-password: - post: - operationId: auth_public_auth_reset_password - x-marreq-variants: [ cloud ] - parameters: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - '400': { $ref: '#/components/responses/ApiError' } - '500': { $ref: '#/components/responses/ApiError' } - description: Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing - email, preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. - security: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: { token: *id005, new_password: *id005 } - required: [ token, new_password ] - additionalProperties: true -components: - securitySchemes: - cookieAuth: - { - type: apiKey, - in: cookie, - name: session, - description: Rocket private session; __Host-session on secure deployments. Mutations require X-CSRF-Token. - } - bearerAuth: - { - type: http, - scheme: bearer, - description: 'Personal API token or delegated OAuth access token; project membership, domain scopes and - project permissions apply.' - } - mcpServiceAuth: - { - type: apiKey, - in: header, - name: X-Marreq-Mcp-Audit-Secret, - description: Private service-to-service audit secret; never browser or delegated caller configuration. - } - schemas: - SemanticSearchResult: - type: object - properties: - id: { type: integer } - rank: { type: integer } - lexical_rank: { type: integer } - vector_rank: { type: integer } - reference_code: { type: string } - title: { type: string } - description: { type: string } - snippet: { type: string } - status: { type: string } - category: { type: string } - applicability: { type: string } - verification: { type: string } - score: { type: number } - required: - - id - - rank - - reference_code - - title - - description - - snippet - - status - - category - - applicability - - verification - - score - additionalProperties: true - RagAnswerResponse: - type: object - properties: - answer: { type: string } - citations: - type: array - items: - type: object - properties: { requirement_id: { type: integer }, reference_code: { type: string }, title: { type: string } } - required: [ requirement_id, reference_code, title ] - additionalProperties: true - results: { type: array, items: { $ref: "#/components/schemas/SemanticSearchResult" } } - required: [ answer, citations, results ] - additionalProperties: true - OAuthTokenRequest: - oneOf: - - type: object - properties: - client_id: { type: string } - resource: { type: string } - grant_type: { type: string, const: authorization_code } - code: { type: string } - redirect_uri: { type: string } - code_verifier: { type: string } - refresh_token: { type: string } - required: [ grant_type, client_id, resource, code, redirect_uri, code_verifier ] - additionalProperties: true - - type: object - properties: - client_id: { type: string } - resource: { type: string } - grant_type: { type: string, const: refresh_token } - refresh_token: { type: string } - code: { type: string } - redirect_uri: { type: string } - code_verifier: { type: string } - required: [ grant_type, client_id, resource, refresh_token ] - additionalProperties: true - ReportDefinition: - type: object - properties: - report_type: { type: string, enum: [ vcd, coverage ] } - document: - type: object - properties: - doc_id: { type: string } - title: { type: string } - issue: { type: string } - revision: { type: string } - classification: { type: string } - watermark: { anyOf: [ { type: string }, { type: "null" } ] } - page_size: { type: string, enum: [ a4, letter ] } - pdf_a: { type: boolean } - signatories: - type: array - items: - type: object - properties: { role: { type: string }, name: { type: string } } - required: [ role, name ] - additionalProperties: true - change_record: - type: array - items: - type: object - properties: { issue: { type: string }, date: { type: string }, changes: { type: string }, author: { type: string } } - required: [ issue, date, changes, author ] - additionalProperties: true - documents: - type: array - items: - type: object - properties: { ref: { type: string }, id: { type: string }, title: { type: string }, issue: { type: string } } - required: [ ref, id, title, issue ] - additionalProperties: true - required: - - doc_id - - title - - issue - - revision - - classification - - watermark - - page_size - - pdf_a - - signatories - - change_record - - documents - additionalProperties: true - sections: - type: array - items: - type: object - properties: { key: { type: string }, enabled: { type: boolean }, options: { type: object, additionalProperties: true } } - required: [ key, enabled ] - additionalProperties: true - required: [ report_type, document, sections ] - additionalProperties: true - ReportDefinitionInput: - type: object - properties: - report_type: { type: string, enum: [ vcd, coverage ] } - document: - type: object - properties: - doc_id: { type: string } - title: { type: string } - issue: { type: string } - revision: { type: string } - classification: { type: string } - watermark: { anyOf: [ { type: string }, { type: "null" } ] } - page_size: { type: string, enum: [ a4, letter ] } - pdf_a: { type: boolean } - signatories: - type: array - items: - type: object - properties: { role: { type: string }, name: { type: string } } - required: [] - additionalProperties: false - change_record: - type: array - items: - type: object - properties: { issue: { type: string }, date: { type: string }, changes: { type: string }, author: { type: string } } - required: [] - additionalProperties: false - documents: - type: array - items: - type: object - properties: { ref: { type: string }, id: { type: string }, title: { type: string }, issue: { type: string } } - required: [] - additionalProperties: false - required: [] - additionalProperties: false - sections: - type: array - items: - type: object - properties: { key: { type: string }, enabled: { type: boolean }, options: { type: object, additionalProperties: {} } } - required: [ key ] - additionalProperties: false - required: [ report_type ] - additionalProperties: false - ReportOptionSpec: - oneOf: - - type: object - properties: - key: { type: string } - label: { type: string } - default: {} - kind: { type: string, const: select } - choices: - type: array - items: - type: object - properties: { value: { type: string }, label: { type: string } } - required: [ value, label ] - additionalProperties: true - required: [ key, label, default, kind, choices ] - additionalProperties: true - - type: object - properties: - key: { type: string } - label: { type: string } - default: {} - kind: { type: string, const: multi_select } - choices: - type: array - items: - type: object - properties: { value: { type: string }, label: { type: string } } - required: [ value, label ] - additionalProperties: true - required: [ key, label, default, kind, choices ] - additionalProperties: true - - type: object - properties: - key: { type: string } - label: { type: string } - default: {} - kind: { type: string, const: text } - multiline: { type: boolean } - required: [ key, label, default, kind, multiline ] - additionalProperties: true - ReportSectionSpec: - type: object - properties: - key: { type: string } - title: { type: string } - description: { type: string } - report_types: { type: array, items: { type: string, enum: [ vcd, coverage ] } } - default_enabled: { type: boolean } - options: { type: array, items: { $ref: "#/components/schemas/ReportOptionSpec" } } - required: [ key, title, description, report_types, default_enabled, options ] - additionalProperties: true - ReportTemplateView: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - owner_id: { type: integer } - name: { type: string } - report_type: - type: string - enum: - - vcd - - coverage - visibility: - type: string - enum: - - private - - shared - owner_name: { type: string } - created_at: { type: string } - updated_at: { type: string } - can_edit: { type: boolean } - definition: {} - required: - - id - - project_id - - owner_id - - name - - report_type - - visibility - - owner_name - - created_at - - updated_at - - can_edit - - definition - additionalProperties: true - ReportTemplateInput: - type: object - properties: - name: { anyOf: [ { type: string }, { type: "null" } ] } - visibility: { anyOf: [ { type: string }, { type: "null" } ] } - definition: { anyOf: [ { $ref: "#/components/schemas/ReportDefinitionInput" }, { type: "null" } ] } - required: [] - additionalProperties: false - ExcelImportResult: - type: object - properties: - success: { type: boolean } - message: { type: string } - imported_count: { type: integer } - errors: { type: array, items: { type: string } } - imported_requirement_ids: { type: array, items: { type: integer } } - required: [ success, message, imported_count, errors, imported_requirement_ids ] - additionalProperties: true - CustomFieldValueDisplay: - type: object - properties: - field_id: { type: integer } - label: { type: string } - value: { anyOf: [ { type: string }, { type: "null" } ] } - required: [ field_id, label, value ] - additionalProperties: true - Requirement: - type: object - properties: - id: { type: integer } - status_id: { type: integer } - author_id: { type: integer } - reviewer_id: { type: integer } - category_id: { type: integer } - applicability_id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - reference_code: { type: string } - creation_date: { type: string } - update_date: { type: string } - current_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } - parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } - approved_by: { anyOf: [ { type: integer }, { type: "null" } ] } - deadline_date: { anyOf: [ { type: string }, { type: "null" } ] } - justification: { anyOf: [ { type: string }, { type: "null" } ] } - approved_at: { anyOf: [ { type: string }, { type: "null" } ] } - approval_state: { type: string, enum: [ draft, reviewed, approved ] } - same_as_current: { type: boolean } - custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueDisplay" } } - required: - - id - - status_id - - author_id - - reviewer_id - - category_id - - applicability_id - - project_id - - title - - description - - reference_code - - creation_date - - update_date - - current_version_id - - parent_id - - approved_by - - deadline_date - - justification - - approved_at - - approval_state - additionalProperties: true - RequirementVersion: - type: object - properties: - id: { type: integer } - requirement_id: { type: integer } - status_id: { type: integer } - author_id: { type: integer } - reviewer_id: { type: integer } - category_id: { type: integer } - applicability_id: { type: integer } - title: { type: string } - description: { type: string } - created_at: { type: string } - approved_by: { anyOf: [ { type: integer }, { type: "null" } ] } - reviewed_by: { anyOf: [ { type: integer }, { type: "null" } ] } - justification: { anyOf: [ { type: string }, { type: "null" } ] } - deadline_date: { anyOf: [ { type: string }, { type: "null" } ] } - approved_at: { anyOf: [ { type: string }, { type: "null" } ] } - reviewed_at: { anyOf: [ { type: string }, { type: "null" } ] } - approval_state: { type: string, enum: [ draft, reviewed, approved ] } - required: - - id - - requirement_id - - status_id - - author_id - - reviewer_id - - category_id - - applicability_id - - title - - description - - created_at - - approved_by - - reviewed_by - - justification - - deadline_date - - approved_at - - reviewed_at - - approval_state - additionalProperties: true - RequirementListRow: - allOf: - - { $ref: "#/components/schemas/Requirement" } - - type: object - properties: - verification_method_ids: { type: array, items: { type: integer } } - parent_requirement_ids: { type: array, items: { type: integer } } - required: [ verification_method_ids, parent_requirement_ids ] - additionalProperties: true - RequirementWithTraceSummary: - allOf: - - { $ref: "#/components/schemas/Requirement" } - - type: object - properties: - trace_summary: - type: object - properties: - parent_links: { type: array, items: { $ref: "#/components/schemas/RequirementVersionLink" } } - child_ids: { type: array, items: { type: integer } } - linked_test_ids: { type: array, items: { type: integer } } - required: [ parent_links, child_ids, linked_test_ids ] - additionalProperties: true - required: [ trace_summary ] - additionalProperties: true - RequirementVersionDetail: - allOf: - - { $ref: "#/components/schemas/RequirementVersion" } - - type: object - properties: - custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueDisplay" } } - verification_method_ids: { type: array, items: { type: integer } } - required: [ custom_fields, verification_method_ids ] - additionalProperties: true - CustomFieldValueInput: - type: object - properties: { field_id: { type: integer }, value: { anyOf: [ { type: string }, { type: "null" } ] } } - required: [ field_id ] - additionalProperties: true - RequirementCreateRequest: - type: object - properties: - title: { type: string } - description: { type: string } - reference_code: { type: string } - author_id: { type: integer } - category_id: { type: integer } - status_id: { type: integer } - reviewer_id: { type: integer } - applicability_id: { type: integer } - project_id: { type: integer } - justification: { anyOf: [ { type: string }, { type: "null" } ] } - verification_method_ids: { type: array, items: { type: integer }, minItems: 1 } - custom_fields: { type: array, items: { $ref: "#/components/schemas/CustomFieldValueInput" } } - parent_links: - type: array - items: - type: object - properties: - target_version_id: { type: integer } - link_type: { type: string } - rationale: { anyOf: [ { type: string }, { type: "null" } ] } - required: [ target_version_id, link_type ] - additionalProperties: true - required: - - title - - description - - reference_code - - author_id - - category_id - - status_id - - reviewer_id - - applicability_id - - project_id - - verification_method_ids - additionalProperties: true - RequirementPatch: - type: object - properties: - title: { anyOf: [ { type: string }, { type: "null" } ] } - description: { anyOf: [ { type: string }, { type: "null" } ] } - justification: { anyOf: [ { type: string }, { type: "null" } ] } - status_id: { anyOf: [ { type: integer }, { type: "null" } ] } - author_id: { anyOf: [ { type: integer }, { type: "null" } ] } - reviewer_id: { anyOf: [ { type: integer }, { type: "null" } ] } - category_id: { anyOf: [ { type: integer }, { type: "null" } ] } - applicability_id: { anyOf: [ { type: integer }, { type: "null" } ] } - verification_method_ids: { anyOf: [ { type: array, items: { type: integer } }, { type: "null" } ] } - custom_fields: - { - anyOf: [ { type: array, items: { $ref: "#/components/schemas/CustomFieldValueInput" } }, { type: "null" } ] - } - required: [] - additionalProperties: true - Verification: - type: object - properties: - id: { type: integer } - status_id: { type: integer } - project_id: { type: integer } - author_id: { type: integer } - reviewer_id: { type: integer } - name: { type: string } - reference_code: { type: string } - description: { type: string } - source: { type: string } - parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } - verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } - status_set_by: { anyOf: [ { type: integer }, { type: "null" } ] } - status_set_at: { anyOf: [ { type: string }, { type: "null" } ] } - required: - - id - - status_id - - project_id - - author_id - - reviewer_id - - name - - reference_code - - description - - source - - parent_id - - verification_method_id - - status_set_by - - status_set_at - additionalProperties: true - NewVerification: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - status_id: { type: integer } - project_id: { type: integer } - author_id: { type: integer } - reviewer_id: { type: integer } - name: { type: string } - reference_code: { type: string } - description: { type: string } - source: { type: string } - parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } - verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ status_id, project_id, author_id, reviewer_id, name, reference_code, description, source ] - additionalProperties: true - Baseline: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - created_by: { type: integer } - name: { type: string } - created_at: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - source_saved_view_id: { anyOf: [ { type: integer }, { type: "null" } ] } - source_view_definition: { anyOf: [ {}, { type: "null" } ] } - required: - - id - - project_id - - created_by - - name - - created_at - - description - - source_saved_view_id - - source_view_definition - additionalProperties: true - BaselineTraceability: - type: object - properties: - baseline_id: { type: integer } - requirement_id: { type: integer } - verification_id: { type: integer } - suspect: { type: boolean } - suspect_at: { anyOf: [ { type: string }, { type: "null" } ] } - suspect_reason: { anyOf: [ { type: string }, { type: "null" } ] } - required: [ baseline_id, requirement_id, verification_id, suspect, suspect_at, suspect_reason ] - additionalProperties: true - BaselineVerification: - type: object - properties: - status_id: { type: integer } - project_id: { type: integer } - author_id: { type: integer } - reviewer_id: { type: integer } - name: { type: string } - reference_code: { type: string } - description: { type: string } - source: { type: string } - parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } - verification_method_id: { anyOf: [ { type: integer }, { type: "null" } ] } - baseline_id: { type: integer } - verification_id: { type: integer } - required: - - status_id - - project_id - - author_id - - reviewer_id - - name - - reference_code - - description - - source - - parent_id - - verification_method_id - - baseline_id - - verification_id - additionalProperties: true - BaselineDiff: - type: object - properties: - requirements_only_in_a: { type: array, items: { type: integer } } - requirements_only_in_b: { type: array, items: { type: integer } } - trace_only_in_a: { type: array, items: { type: array, items: { type: integer }, minItems: 2, maxItems: 2 } } - trace_only_in_b: { type: array, items: { type: array, items: { type: integer }, minItems: 2, maxItems: 2 } } - use_cases_only_in_a: { type: array, items: { type: integer } } - use_cases_only_in_b: { type: array, items: { type: integer } } - use_cases_changed: - type: array - items: - type: object - properties: { use_case_id: { type: integer }, version_a: { type: integer }, version_b: { type: integer } } - required: [ use_case_id, version_a, version_b ] - additionalProperties: true - required: - - requirements_only_in_a - - requirements_only_in_b - - trace_only_in_a - - trace_only_in_b - - use_cases_only_in_a - - use_cases_only_in_b - - use_cases_changed - additionalProperties: true - TextDiffResult: - type: object - properties: - added: { type: array, items: { type: string } } - removed: { type: array, items: { type: string } } - unchanged: { type: array, items: { type: string } } - required: [ added, removed, unchanged ] - additionalProperties: true - SingleValueDiff: - type: object - properties: - old_id: { type: integer } - new_id: { type: integer } - unchanged: { type: integer } - unchanged_label: { type: string } - old_label: { type: string } - new_label: { type: string } - required: [] - additionalProperties: true - VerificationDiff: - type: object - properties: - added_ids: { type: array, items: { type: integer } } - removed_ids: { type: array, items: { type: integer } } - unchanged_ids: { type: array, items: { type: integer } } - added_labels: { type: array, items: { type: string } } - removed_labels: { type: array, items: { type: string } } - unchanged_labels: { type: array, items: { type: string } } - required: [ added_ids, removed_ids, unchanged_ids ] - additionalProperties: true - RequirementDiff: - type: object - properties: - text: - type: object - properties: - title: { $ref: "#/components/schemas/TextDiffResult" } - description: { $ref: "#/components/schemas/TextDiffResult" } - justification: { $ref: "#/components/schemas/TextDiffResult" } - required: [ title, description, justification ] - additionalProperties: true - metadata: - type: object - properties: - status: { $ref: "#/components/schemas/SingleValueDiff" } - category: { $ref: "#/components/schemas/SingleValueDiff" } - applicability: { $ref: "#/components/schemas/SingleValueDiff" } - verification: { $ref: "#/components/schemas/VerificationDiff" } - custom_fields: - type: array - items: - type: object - properties: - field_id: { type: integer } - label: { type: string } - old_value: { anyOf: [ { type: string }, { type: "null" } ] } - new_value: { anyOf: [ { type: string }, { type: "null" } ] } - unchanged: { type: boolean } - required: [ field_id, label, old_value, new_value, unchanged ] - additionalProperties: true - required: [ status, category, applicability, verification, custom_fields ] - additionalProperties: true - required: [ text, metadata ] - additionalProperties: true - VerificationVersionDiff: - type: object - properties: - text: - type: object - properties: - name: { $ref: "#/components/schemas/TextDiffResult" } - description: { $ref: "#/components/schemas/TextDiffResult" } - source: { $ref: "#/components/schemas/TextDiffResult" } - reference_code: { $ref: "#/components/schemas/TextDiffResult" } - required: [ name, description, source, reference_code ] - additionalProperties: true - metadata: - type: object - properties: - status: { $ref: "#/components/schemas/SingleValueDiff" } - verification_method: { $ref: "#/components/schemas/SingleValueDiff" } - parent: { $ref: "#/components/schemas/SingleValueDiff" } - required: [ status, verification_method, parent ] - additionalProperties: true - required: [ text, metadata ] - additionalProperties: true - Dsm: - type: object - properties: - order: { type: string } - link_types: { type: array, items: { type: string } } - requirements: - type: array - items: - type: object - properties: - id: { type: integer } - index: { type: integer } - category_id: { type: integer } - depth: { type: integer } - reference_code: { type: string } - title: { type: string } - approval_state: { type: string } - category: { type: string } - parent_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ id, index, category_id, depth, reference_code, title, approval_state, category, parent_id ] - additionalProperties: true - cells: - type: array - items: - type: object - properties: - row: { type: integer } - col: { type: integer } - link_types: { type: array, items: { type: string } } - link_ids: { type: array, items: { type: integer } } - upstream_changed: { type: boolean } - in_loop: { type: boolean } - required: [ row, col, link_types, link_ids, upstream_changed, in_loop ] - additionalProperties: true - groups: - type: array - items: - type: object - properties: { label: { type: string }, start: { type: integer }, end: { type: integer } } - required: [ label, start, end ] - additionalProperties: true - loops: - type: array - items: - type: object - properties: - requirement_ids: { type: array, items: { type: integer } } - path: { type: array, items: { type: integer } } - required: [ requirement_ids, path ] - additionalProperties: true - stats: - type: object - properties: - requirements: { type: integer } - links: { type: integer } - cells: { type: integer } - loops: { type: integer } - upstream_changed: { type: integer } - external_links: { type: integer } - required: [ requirements, links, cells, loops, upstream_changed, external_links ] - additionalProperties: true - required: [ order, link_types, requirements, cells, groups, loops, stats ] - additionalProperties: true - EntityActivityItem: - type: object - properties: - log_id: { type: integer } - user_id: { type: integer } - username: { type: string } - action_type: { type: string } - summary: { type: string } - created_at: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - changes: { type: array, items: { $ref: "#/components/schemas/LogChangeDetail" } } - required: [ log_id, user_id, username, action_type, summary, created_at, description, changes ] - additionalProperties: true - MatrixLink: - type: object - properties: - req_id: { type: integer } - verification_id: { type: integer } - project_id: { type: integer } - creation_date: { type: string } - suspect: { type: boolean } - suspect_at: { anyOf: [ { type: string }, { type: "null" } ] } - suspect_reason: { anyOf: [ { type: string }, { type: "null" } ] } - cleared_at: { anyOf: [ { type: string }, { type: "null" } ] } - cleared_by: { anyOf: [ { type: integer }, { type: "null" } ] } - triggering_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } - triggering_user_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: - - req_id - - verification_id - - project_id - - creation_date - - suspect - - suspect_at - - suspect_reason - - cleared_at - - cleared_by - - triggering_version_id - - triggering_user_id - additionalProperties: true - VerificationControlView: - type: object - properties: - verification_id: { type: integer } - verification_level: { anyOf: [ { type: string }, { type: "null" } ] } - verification_stage: { anyOf: [ { type: string }, { type: "null" } ] } - evidence_reference: { anyOf: [ { type: string }, { type: "null" } ] } - updated_by: { anyOf: [ { type: integer }, { type: "null" } ] } - updated_at: { anyOf: [ { type: string }, { type: "null" } ] } - suggestions: - type: object - properties: { levels: { type: array, items: { type: string } }, stages: { type: array, items: { type: string } } } - required: [ levels, stages ] - additionalProperties: true - required: - - verification_id - - verification_level - - verification_stage - - evidence_reference - - updated_by - - updated_at - - suggestions - additionalProperties: true - RequirementCloseOutView: - type: object - properties: - requirement_id: { type: integer } - compliance: - anyOf: - - type: string - enum: - - C - - PC - - NC - - type: "null" - note: { anyOf: [ { type: string }, { type: "null" } ] } - set_by: { anyOf: [ { type: integer }, { type: "null" } ] } - set_at: { anyOf: [ { type: string }, { type: "null" } ] } - close_out: - type: object - properties: { status: { type: string, enum: [ open, closed ] }, reason: { type: string } } - required: [ status, reason ] - additionalProperties: true - verifications: - type: array - items: - type: object - properties: - id: { type: integer } - reference_code: { type: string } - name: { type: string } - status_id: { type: integer } - status_title: { type: string } - outcome: { type: string, enum: [ passed, failed, in_progress, not_run ] } - required: [ id, reference_code, name, status_id, status_title, outcome ] - additionalProperties: true - required: [ requirement_id, compliance, note, set_by, set_at, close_out, verifications ] - additionalProperties: true - VerificationControlInput: - type: object - properties: - verification_level: { anyOf: [ { type: string }, { type: "null" } ] } - verification_stage: { anyOf: [ { type: string }, { type: "null" } ] } - evidence_reference: { anyOf: [ { type: string }, { type: "null" } ] } - required: [] - additionalProperties: false - RequirementVersionLink: - type: object - properties: - id: { type: integer } - source_version_id: { type: integer } - target_version_id: { type: integer } - project_id: { type: integer } - link_type: { type: string } - created_at: { type: string } - rationale: { anyOf: [ { type: string }, { type: "null" } ] } - metadata: { anyOf: [ {}, { type: "null" } ] } - required: [ id, source_version_id, target_version_id, project_id, link_type, created_at, rationale, metadata ] - additionalProperties: true - CommentResponse: - type: object - properties: - id: { type: integer } - requirement_id: { type: integer } - author_id: { type: integer } - requirement_version_id: { anyOf: [ { type: integer }, { type: "null" } ] } - author_name: { type: string } - body: { type: string } - created_at: { type: string } - required: [ id, requirement_id, author_id, requirement_version_id, author_name, body, created_at ] - additionalProperties: true - McpAuditRequest: - type: object - properties: - project_id: { anyOf: [ { type: integer }, { type: "null" } ] } - session_id: { anyOf: [ { type: string }, { type: "null" } ] } - tool_name: - type: string - enum: - - list_projects - - get_requirement - - list_requirements - - get_versions - - semantic_search_requirements - - compare_versions - - trace_up - - trace_down - - coverage_report - - dependency_matrix - - get_baseline - - diff_baselines - - list_verifications - - get_verification - - list_baselines - - get_requirement_activity - - get_verification_activity - - list_requirement_comments - - get_verification_matrix - - list_project_catalog - - diff_baseline_vs_current - - create_requirement - - patch_requirement - - set_approval - - create_baseline - - create_requirement_comment - - create_verification - - update_verification - - put_verification_matrix - - clear_suspect - params_summary: { anyOf: [ { type: string }, { type: "null" } ] } - result_summary: { anyOf: [ { type: string }, { type: "null" } ] } - is_write: { type: boolean } - required: [ tool_name, is_write ] - additionalProperties: true - ProjectStatus: { type: string, enum: [ Active, Completed, OnHold, Cancelled ] } - Project: - type: object - properties: - id: { type: integer } - name: { type: string } - slug: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - creation_date: { anyOf: [ { type: string }, { type: "null" } ] } - update_date: { anyOf: [ { type: string }, { type: "null" } ] } - archived_at: { anyOf: [ { type: string }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - archived_by: { anyOf: [ { type: integer }, { type: "null" } ] } - status: { $ref: "#/components/schemas/ProjectStatus" } - required: - - id - - name - - slug - - description - - creation_date - - update_date - - archived_at - - owner_id - - group_id - - archived_by - - status - additionalProperties: true - GroupResponse: - type: object - properties: - id: { type: integer } - name: { type: string } - slug: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - created_at: { type: string } - updated_at: { type: string } - required: [ id, name, slug, description, owner_id, created_at, updated_at ] - additionalProperties: true - GroupMemberResponse: - type: object - properties: { user_id: { type: integer }, role: { type: integer }, role_label: { type: string } } - required: [ user_id, role, role_label ] - additionalProperties: true - Notification: - type: object - properties: - id: { type: integer } - user_id: { type: integer } - project_id: { anyOf: [ { type: integer }, { type: "null" } ] } - entity_id: { anyOf: [ { type: integer }, { type: "null" } ] } - actor_id: { anyOf: [ { type: integer }, { type: "null" } ] } - notification_type: { type: string } - title: { type: string } - created_at: { type: string } - body: { anyOf: [ { type: string }, { type: "null" } ] } - entity_type: { anyOf: [ { type: string }, { type: "null" } ] } - read: { type: boolean } - emailed: { type: boolean } - required: - - id - - user_id - - project_id - - entity_id - - actor_id - - notification_type - - title - - created_at - - body - - entity_type - - read - - emailed - additionalProperties: true - NotificationPreference: - type: object - properties: - id: { type: integer } - user_id: { type: integer } - project_id: { type: integer } - notify_in_app: { type: boolean } - notify_email: { type: boolean } - required: [ id, user_id, project_id, notify_in_app, notify_email ] - additionalProperties: true - MemberResponse: - type: object - properties: - user_id: { type: integer } - role: { type: integer } - role_label: { type: string } - username: { type: string } - name: { type: string } - required: [ user_id, role, role_label, username, name ] - additionalProperties: true - ProjectReviewersResponse: - type: object - properties: { user_ids: { type: array, items: { type: integer } } } - required: [ user_ids ] - additionalProperties: true - EffectivePermissions: - type: object - properties: - view_requirements: { type: boolean } - edit_requirements: { type: boolean } - approve_versions: { type: boolean } - is_project_reviewer: { type: boolean } - manage_custom_fields: { type: boolean } - manage_project_configuration: { type: boolean } - manage_project_members: { type: boolean } - archived: { type: boolean } - required: - - view_requirements - - edit_requirements - - approve_versions - - is_project_reviewer - - manage_custom_fields - - manage_project_configuration - - manage_project_members - - archived - additionalProperties: true - CustomFieldDefinition: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - label: { type: string } - field_type: - type: string - enum: - - text - - enum - - boolean - - number - enum_values: { anyOf: [ {}, { type: "null" } ] } - sort_order: { type: integer } - created_at: { type: string } - required: [ id, project_id, label, field_type, enum_values, sort_order, created_at ] - additionalProperties: true - CustomFieldDefinitionPayload: - type: object - properties: - label: { type: string } - field_type: - type: string - enum: - - text - - enum - - boolean - - number - enum_values: { anyOf: [ { type: array, items: { type: string } }, { type: "null" } ] } - sort_order: { anyOf: [ { type: integer }, { type: "null" } ] } - required: [ label, field_type ] - additionalProperties: true - SavedView: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - owner_id: { type: integer } - name: { type: string } - visibility: - type: string - enum: - - private - - shared - created_at: { type: string } - updated_at: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - locked_at: { anyOf: [ { type: string }, { type: "null" } ] } - locked: { type: boolean } - definition: {} - required: - - id - - project_id - - owner_id - - name - - visibility - - created_at - - updated_at - - description - - locked_at - - locked - - definition - additionalProperties: true - SavedViewPayload: - type: object - properties: - name: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - visibility: - type: string - enum: - - private - - shared - definition: {} - required: [ name, visibility, definition ] - additionalProperties: true - DashboardProject: - type: object - properties: - project_id: { type: integer } - requirements_count: { type: integer } - tests_count: { type: integer } - project_slug: { type: string } - project_base_path: { type: string } - name: { type: string } - status_id: { type: string } - project_status_normalized: { type: string } - project_status_badge: { type: string } - project_initial: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - creation_date: { anyOf: [ { type: string }, { type: "null" } ] } - update_date: { anyOf: [ { type: string }, { type: "null" } ] } - archived_at: { anyOf: [ { type: string }, { type: "null" } ] } - project_owner_name: { anyOf: [ { type: string }, { type: "null" } ] } - group_name: { anyOf: [ { type: string }, { type: "null" } ] } - group_slug: { anyOf: [ { type: string }, { type: "null" } ] } - group_path: { anyOf: [ { type: string }, { type: "null" } ] } - role_label: { anyOf: [ { type: string }, { type: "null" } ] } - owner_id: { anyOf: [ { type: integer }, { type: "null" } ] } - group_id: { anyOf: [ { type: integer }, { type: "null" } ] } - role_id: { anyOf: [ { type: integer }, { type: "null" } ] } - archived: { type: boolean } - can_view_group: { type: boolean } - required: - - project_id - - requirements_count - - tests_count - - project_slug - - project_base_path - - name - - status_id - - project_status_normalized - - project_status_badge - - project_initial - - description - - creation_date - - update_date - - archived_at - - project_owner_name - - group_name - - group_slug - - group_path - - role_label - - owner_id - - group_id - - role_id - - archived - - can_view_group - additionalProperties: true - AttentionItem: - type: object - properties: - project_id: { type: integer } - title: { type: string } - requirement_id: { anyOf: [ { type: integer }, { type: "null" } ] } - use_case_id: { anyOf: [ { type: integer }, { type: "null" } ] } - count: { anyOf: [ { type: integer }, { type: "null" } ] } - notification_id: { anyOf: [ { type: integer }, { type: "null" } ] } - reference_code: { anyOf: [ { type: string }, { type: "null" } ] } - notification_type: { anyOf: [ { type: string }, { type: "null" } ] } - at: { anyOf: [ { type: string }, { type: "null" } ] } - kind: { type: string, enum: [ approval, review, suspect, notification ] } - required: - - project_id - - title - - requirement_id - - use_case_id - - count - - notification_id - - reference_code - - notification_type - - at - - kind - additionalProperties: true - Applicability: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ id, project_id, title, description, tag ] - additionalProperties: true - NewApplicability: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ title, description, tag, project_id ] - additionalProperties: true - Category: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ id, project_id, title, description, tag ] - additionalProperties: true - NewCategory: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ title, description, tag, project_id ] - additionalProperties: true - VerificationMethod: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ id, project_id, title, description, tag ] - additionalProperties: true - NewVerificationMethod: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - required: [ title, description, tag, project_id ] - additionalProperties: true - RequirementStatus: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - is_system: { type: boolean } - tag_color: { anyOf: [ { type: string }, { type: "null" } ] } - required: [ id, project_id, title, description, tag, is_system, tag_color ] - additionalProperties: true - NewRequirementStatus: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - is_system: { type: boolean } - tag_color: { anyOf: [ { type: string }, { type: "null" } ] } - required: [ title, description, tag, project_id ] - additionalProperties: true - VerificationStatus: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - is_system: { type: boolean } - tag_color: { anyOf: [ { type: string }, { type: "null" } ] } - outcome: - type: string - enum: - - passed - - failed - - in_progress - - not_run - required: [ id, project_id, title, description, tag, is_system, tag_color, outcome ] - additionalProperties: true - NewVerificationStatus: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { type: integer } - title: { type: string } - description: { type: string } - tag: { type: string } - is_system: { type: boolean } - tag_color: { anyOf: [ { type: string }, { type: "null" } ] } - outcome: { anyOf: [ { type: string, enum: [ passed, failed, in_progress, not_run ] }, { type: "null" } ] } - required: [ title, description, tag, project_id ] - additionalProperties: true - UserCreateRequest: - type: object - properties: - username: { type: string } - name: { type: string } - email: { type: string } - password: { type: string } - is_admin: { type: boolean } - required: [ username, name, email, password, is_admin ] - additionalProperties: true - UpdateUser: - type: object - properties: - id: { anyOf: [ { type: integer }, { type: "null" } ] } - username: { type: string } - name: { type: string } - email: { type: string } - is_admin: { type: boolean } - required: [ username, name, email, is_admin ] - additionalProperties: true - BackupInfo: - type: object - properties: - attachments_available: { type: boolean } - attachment_files: { type: integer, minimum: 0 } - attachment_bytes: { type: integer, minimum: 0 } - required: [ attachments_available, attachment_files, attachment_bytes ] - additionalProperties: true - LogChangeDetail: - type: object - properties: { field: { type: string }, old_value: { type: string }, new_value: { type: string } } - required: [ field, old_value, new_value ] - additionalProperties: true - AdminLogItem: - type: object - properties: - log_id: { type: integer } - user_id: { type: integer } - username: { type: string } - action_type: { type: string } - summary: { type: string } - description: { anyOf: [ { type: string }, { type: "null" } ] } - created_at: { type: string } - changes: { type: array, items: { $ref: "#/components/schemas/LogChangeDetail" } } - entity_type: { type: string } - entity_id: { anyOf: [ { type: integer }, { type: "null" } ] } - project_id: { anyOf: [ { type: integer }, { type: "null" } ] } - required: - - log_id - - user_id - - username - - action_type - - summary - - description - - created_at - - changes - - entity_type - - entity_id - - project_id - additionalProperties: true - AdminLogList: - type: object - properties: - items: { type: array, items: { $ref: "#/components/schemas/AdminLogItem" } } - total: { type: integer } - limit: { type: integer } - offset: { type: integer } - required: [ items, total, limit, offset ] - additionalProperties: true - LogStatsResponse: - type: object - properties: - since: { type: string } - until: { type: string } - total: { type: integer } - active_users: { type: integer } - by_day: - type: array - items: - type: object - properties: { day: { type: string, format: date }, count: { type: integer } } - required: [ day, count ] - additionalProperties: true - by_action: - type: array - items: - type: object - properties: { action_type: { type: string }, count: { type: integer } } - required: [ action_type, count ] - additionalProperties: true - by_user: - type: array - items: - type: object - properties: { user_id: { type: integer }, username: { type: string }, count: { type: integer } } - required: [ user_id, username, count ] - additionalProperties: true - required: [ since, until, total, active_users, by_day, by_action, by_user ] - additionalProperties: true - AttachmentView: - type: object - properties: - id: { type: integer } - entity_type: { type: string, enum: [ requirement, verification ] } - entity_id: { type: integer } - filename: { type: string } - content_type: { type: string } - size_bytes: { type: integer } - uploaded_by: { type: [ integer, "null" ] } - uploaded_by_name: { type: [ string, "null" ] } - created_at: { type: string } - deleted: { type: boolean } - required: - - id - - entity_type - - entity_id - - filename - - content_type - - size_bytes - - uploaded_by - - uploaded_by_name - - created_at - - deleted - additionalProperties: true - AttachmentStorage: - type: object - properties: - used_bytes: { type: integer } - quota_bytes: { type: integer } - quota_is_default: { type: boolean } - default_quota_bytes: { type: integer } - retained_by_baselines_bytes: { type: integer } - max_file_bytes: { type: integer } - allowed_extensions: { type: array, items: { type: string } } - required: - - used_bytes - - quota_bytes - - quota_is_default - - default_quota_bytes - - retained_by_baselines_bytes - - max_file_bytes - - allowed_extensions - additionalProperties: true - AuthProfileUpdate: - type: object - properties: { name: { type: string }, email: { type: string }, current_password: { type: [ string, "null" ] } } - required: [ name, email ] - additionalProperties: true - AuthIdentities: - type: object - properties: - password_configured: { type: boolean } - identities: - type: array - items: - type: object - properties: - id: { type: integer } - provider: { type: string } - created_at: { type: string } - last_login_at: { type: [ string, "null" ] } - required: [ id, provider, created_at, last_login_at ] - additionalProperties: true - required: [ password_configured, identities ] - additionalProperties: true - AuthChangePassword: - type: object - properties: { current_password: { type: string }, new_password: { type: string }, confirm_password: { type: string } } - required: [ current_password, new_password, confirm_password ] - additionalProperties: true - AuthStatus: - type: object - properties: { status: { type: string, const: ok } } - required: [ status ] - additionalProperties: true - CacheStats: - type: object - properties: - total_entries: { type: integer, minimum: 0 } - active_entries: { type: integer, minimum: 0 } - expired_entries: { type: integer, minimum: 0 } - hits: { type: integer, minimum: 0 } - misses: { type: integer, minimum: 0 } - total_requests: { type: integer, minimum: 0 } - average_access_time_ns: { type: integer, minimum: 0 } - total_access_time_ns: { type: integer, minimum: 0 } - cache_size_bytes: { type: integer, minimum: 0 } - hit_rate: { type: number } - miss_rate: { type: number } - last_cleanup: { type: string, format: date-time } - required: - - total_entries - - active_entries - - expired_entries - - hits - - misses - - total_requests - - average_access_time_ns - - total_access_time_ns - - cache_size_bytes - - hit_rate - - miss_rate - - last_cleanup - additionalProperties: true - CacheRecommendations: - type: object - properties: - recommendations: { type: array, items: { type: string } } - priority: { type: string, enum: [ high, medium, low ] } - analysis_timestamp: { type: string, format: date-time } - required: [ recommendations, priority, analysis_timestamp ] - additionalProperties: true - CacheActionResult: - type: object - properties: { message: { type: string }, timestamp: { type: string, format: date-time } } - required: [ message, timestamp ] - additionalProperties: true - CacheCleanupResult: - allOf: - - { $ref: "#/components/schemas/CacheActionResult" } - - type: object - properties: { cleaned_entries: { type: integer, minimum: 0 } } - required: [ cleaned_entries ] - additionalProperties: true - external_ProviderDiscovery_response: - type: object - properties: - password_enabled: { type: boolean } - external: { type: array, items: { $ref: '#/components/schemas/external_PublicAuthProvider_response' } } - additionalProperties: true - required: [ password_enabled, external ] - external_PublicAuthProvider_response: - type: object - properties: { id: { type: string }, display_name: { type: string } } - additionalProperties: true - required: [ id, display_name ] - entities_User_response: - type: object - properties: - id: { type: integer } - username: { type: string } - name: { type: string } - email: { type: string } - creation_date: { type: string } - last_login: { type: string } - is_admin: { type: boolean } - email_verified: { type: boolean } - additionalProperties: true - required: [ id, username, name, email, creation_date, last_login, is_admin, email_verified ] - use_cases_UseCaseRow_response: - type: object - properties: - id: { type: integer } - reference_code: { type: string } - title: { type: string } - primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } - step_count: { type: integer } - alternative_count: { type: integer } - exception_count: { type: integer } - approval_state: { anyOf: [ { type: string }, { type: 'null' } ] } - current_version_number: { anyOf: [ { type: integer }, { type: 'null' } ] } - has_draft: { type: boolean } - link_count: { type: integer } - suspect_count: { type: integer } - updated_at: { type: string } - additionalProperties: true - required: - [ - id, - reference_code, - title, - primary_actor, - step_count, - alternative_count, - exception_count, - approval_state, - current_version_number, - has_draft, - link_count, - suspect_count, - updated_at - ] - use_cases_NextCode_response: - type: object - properties: { reference_code: { type: string } } - additionalProperties: true - required: [ reference_code ] - use_cases_UseCaseDetail_response: - type: object - properties: - id: { type: integer } - project_id: { type: integer } - reference_code: { type: string } - title: { type: string } - created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - created_at: { type: string } - updated_at: { type: string } - current_version: { anyOf: [ { $ref: '#/components/schemas/use_cases_VersionView_response' }, { type: 'null' } ] } - draft: { anyOf: [ { $ref: '#/components/schemas/use_cases_DraftSummary_response' }, { type: 'null' } ] } - additionalProperties: true - required: [ id, project_id, reference_code, title, created_by, created_at, updated_at, current_version, draft ] - use_cases_VersionView_response: - type: object - properties: - id: { type: integer } - version_number: { type: integer } - approval_state: { type: string } - title: { type: string } - primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } - change_note: { anyOf: [ { type: string }, { type: 'null' } ] } - created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - created_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } - created_at: { type: string } - reviewed_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - reviewed_at: { anyOf: [ { type: string }, { type: 'null' } ] } - approved_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - approved_at: { anyOf: [ { type: string }, { type: 'null' } ] } - document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } - labels: { type: object, additionalProperties: { type: string } } - additionalProperties: true - required: - [ - id, - version_number, - approval_state, - title, - primary_actor, - change_note, - created_by, - created_by_name, - created_at, - reviewed_by, - reviewed_at, - approved_by, - approved_at, - document, - labels - ] - use_cases_VersionSummary_response: - type: object - properties: - id: { type: integer } - version_number: { type: integer } - approval_state: { type: string } - title: { type: string } - primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } - change_note: { anyOf: [ { type: string }, { type: 'null' } ] } - created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - created_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } - created_at: { type: string } - reviewed_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - reviewed_at: { anyOf: [ { type: string }, { type: 'null' } ] } - approved_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - approved_at: { anyOf: [ { type: string }, { type: 'null' } ] } - additionalProperties: true - required: - [ - id, - version_number, - approval_state, - title, - primary_actor, - change_note, - created_by, - created_by_name, - created_at, - reviewed_by, - reviewed_at, - approved_by, - approved_at - ] - use_cases_DraftSummary_response: - type: object - properties: - revision: { type: integer } - base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } - updated_at: { type: string } - additionalProperties: true - required: [ revision, base_version_id, updated_by, updated_by_name, updated_at ] - use_cases_DraftView_response: - type: object - properties: - use_case_id: { type: integer } - revision: { type: integer } - base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } - updated_at: { type: string } - document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } - labels: { type: object, additionalProperties: { type: string } } - additionalProperties: true - required: [ use_case_id, revision, base_version_id, updated_by, updated_by_name, updated_at, document, labels ] - use_cases_PutDraftRequest_request: - type: object - properties: - base_revision: { anyOf: [ { type: integer }, { type: 'null' } ] } - document: { $ref: '#/components/schemas/UseCaseDocument' } - additionalProperties: true - required: [ document ] - use_cases_DraftChangeView_response: - type: object - properties: - use_case_id: { type: integer } - revision: { type: integer } - base_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - updated_by_name: { anyOf: [ { type: string }, { type: 'null' } ] } - updated_at: { type: string } - document: { $ref: '#/components/schemas/UseCaseDocumentResponse' } - labels: { type: object, additionalProperties: { type: string } } - effects: { type: array, items: { $ref: '#/components/schemas/UseCaseEffect' } } - additionalProperties: true - required: - [ - use_case_id, - revision, - base_version_id, - updated_by, - updated_by_name, - updated_at, - document, - labels, - effects - ] - use_cases_SetApprovalRequest_request: { type: object, properties: { state: { type: string } }, additionalProperties: true, required: [ state ] } - use_cases_LinkView_response: - type: object - properties: - id: { type: integer } - element_id: { anyOf: [ { type: string }, { type: 'null' } ] } - element_label: { anyOf: [ { type: string }, { type: 'null' } ] } - target_type: { type: string, enum: [ requirement, verification ] } - requirement_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - verification_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - target_reference_code: { type: string } - target_title: { type: string } - rationale: { anyOf: [ { type: string }, { type: 'null' } ] } - suspect: { type: boolean } - suspect_at: { anyOf: [ { type: string }, { type: 'null' } ] } - suspect_reason: { anyOf: [ { type: string }, { type: 'null' } ] } - linked_requirement_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - current_requirement_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - created_by: { anyOf: [ { type: integer }, { type: 'null' } ] } - created_at: { type: string } - additionalProperties: true - required: - [ - id, - element_id, - element_label, - target_type, - requirement_id, - verification_id, - target_reference_code, - target_title, - rationale, - suspect, - suspect_at, - suspect_reason, - linked_requirement_version_id, - current_requirement_version_id, - created_by, - created_at - ] - use_cases_ClearSuspectResponse_response: - type: object - properties: { status: { type: string }, cleared: { type: boolean } } - additionalProperties: true - required: [ status, cleared ] - use_cases_UseCaseRef_response: - type: object - properties: - use_case_id: { type: integer } - reference_code: { type: string } - title: { type: string } - link_id: { type: integer } - element_id: { anyOf: [ { type: string }, { type: 'null' } ] } - element_label: { anyOf: [ { type: string }, { type: 'null' } ] } - suspect: { type: boolean } - additionalProperties: true - required: [ use_case_id, reference_code, title, link_id, element_id, element_label, suspect ] - use_cases_UseCaseCommentView_response: - type: object - properties: - id: { type: integer } - use_case_id: { type: integer } - use_case_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - element_id: { anyOf: [ { type: string }, { type: 'null' } ] } - element_label: { anyOf: [ { type: string }, { type: 'null' } ] } - author_id: { type: integer } - author_name: { type: string } - body: { type: string } - created_at: { type: string } - additionalProperties: true - required: - [ - id, - use_case_id, - use_case_version_id, - element_id, - element_label, - author_id, - author_name, - body, - created_at - ] - use_cases_BaselineUseCaseView_response: - type: object - properties: - use_case_id: { type: integer } - reference_code: { type: string } - title: { type: string } - primary_actor: { anyOf: [ { type: string }, { type: 'null' } ] } - version_id: { type: integer } - version_number: { type: integer } - approval_state: { type: string } - current_version_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - current_version_number: { anyOf: [ { type: integer }, { type: 'null' } ] } - link_count: { type: integer } - additionalProperties: true - required: - [ - use_case_id, - reference_code, - title, - primary_actor, - version_id, - version_number, - approval_state, - current_version_id, - current_version_number, - link_count - ] - ApiError: - type: object - required: [ status, error, message ] - properties: { status: { type: integer }, error: { type: string }, message: { type: string } } - additionalProperties: true - BuildInfo: - type: object - properties: - backend_version: &id006 { type: string } - backend_git_sha: *id006 - deployment_mode: { type: string, enum: [ server, cloud ] } - frontend_compatibility: - type: object - properties: { min_version: *id006, max_version: *id006 } - required: [ min_version, max_version ] - additionalProperties: true - required: [ backend_version, backend_git_sha, deployment_mode, frontend_compatibility ] - additionalProperties: true - UseCaseElement: - type: object - properties: { id: &id007 { type: string }, text: *id007 } - additionalProperties: false - required: [ id, text ] - UseCaseBranchEnd: - oneOf: - - type: object - properties: { type: { const: rejoin, type: string }, step: *id007 } - additionalProperties: false - required: [ type, step ] - - type: object - properties: { type: { const: end, type: string }, outcome: { type: string, enum: [ success, failure ] } } - additionalProperties: false - required: [ type, outcome ] - UseCaseDocument: - type: object - properties: - schema: { type: string, const: marreq.use-case.v1 } - title: *id007 - primary_actor: { anyOf: [ *id007, { type: 'null' } ] } - secondary_actors: { type: array, items: *id007 } - summary: { anyOf: [ *id007, { type: 'null' } ] } - preconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - main: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - branches: { type: array, items: { $ref: '#/components/schemas/UseCaseBranch' } } - postconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - additionalProperties: false - required: [ title ] - UseCaseBranch: - type: object - properties: - id: *id007 - kind: { type: string, enum: [ alternative, exception ] } - title: *id007 - from_step: *id007 - end: { $ref: '#/components/schemas/UseCaseBranchEnd' } - steps: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - additionalProperties: false - required: [ id, kind, title, from_step, end ] - UseCaseEffect: - oneOf: - - type: object - properties: - type: { type: string, const: relabelled } - id: *id007 - from: *id007 - to: *id007 - additionalProperties: true - required: [ type, id, from, to ] - - type: object - properties: { type: { type: string, const: removed }, id: *id007 } - additionalProperties: true - required: [ type, id ] - - type: object - properties: { type: { type: string, const: rebranched }, id: *id007, from_step: *id007 } - additionalProperties: true - required: [ type, id, from_step ] - - type: object - properties: { type: { type: string, const: created }, id: *id007 } - additionalProperties: true - required: [ type, id ] - DeploymentInfo: - type: object - properties: - mode: { type: string, enum: [ server, cloud ] } - allows_self_registration: &id008 { type: boolean } - requires_email_verification: *id008 - allows_admin_promotion: *id008 - assigns_personal_workspace: *id008 - allows_self_administered_user_creation: *id008 - allows_database_backup: *id008 - required: - [ - mode, - allows_self_registration, - requires_email_verification, - allows_admin_promotion, - assigns_personal_workspace, - allows_self_administered_user_creation, - allows_database_backup - ] - additionalProperties: true - McpPrincipalResponse: - type: object - properties: - user_id: { type: integer } - authentication_type: { type: string } - principal_id: { type: string } - client_id: { anyOf: [ { type: string }, { type: 'null' } ] } - grant_id: { anyOf: [ { type: integer }, { type: 'null' } ] } - additionalProperties: true - required: [ user_id, authentication_type, principal_id, client_id, grant_id ] - UseCaseDocumentResponse: - type: object - properties: - schema: { type: string, const: marreq.use-case.v1 } - title: &id009 { type: string } - primary_actor: { anyOf: [ *id009, { type: 'null' } ] } - secondary_actors: { type: array, items: *id009 } - summary: { anyOf: [ *id009, { type: 'null' } ] } - preconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - main: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - branches: { type: array, items: { $ref: '#/components/schemas/UseCaseBranchResponse' } } - postconditions: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - additionalProperties: false - required: - [ - schema, - title, - primary_actor, - secondary_actors, - summary, - preconditions, - main, - branches, - postconditions - ] - UseCaseBranchResponse: - type: object - properties: - id: &id010 { type: string } - kind: { type: string, enum: [ alternative, exception ] } - title: *id010 - from_step: *id010 - end: { $ref: '#/components/schemas/UseCaseBranchEnd' } - steps: { type: array, items: { $ref: '#/components/schemas/UseCaseElement' } } - additionalProperties: false - required: [ id, kind, title, from_step, end, steps ] - UseCaseListRef: - oneOf: - - { type: string, enum: [ preconditions, main, postconditions ] } - - type: object - properties: { branch: *id002 } - additionalProperties: true - required: [ branch ] - UseCaseNewElement: - type: object - properties: { id: { anyOf: [ *id002, { type: 'null' } ] }, text: *id002 } - additionalProperties: false - required: [ text ] - UseCaseNewBranch: - type: object - properties: - id: { anyOf: [ *id002, { type: 'null' } ] } - kind: { type: string, enum: [ alternative, exception ] } - title: *id002 - from_step: *id002 - end: { $ref: '#/components/schemas/UseCaseBranchEnd' } - steps: { type: array, items: { $ref: '#/components/schemas/UseCaseNewElement' } } - additionalProperties: false - required: [ kind, title, from_step, end ] - UseCaseDependents: - oneOf: - - { type: string, enum: [ cascade ] } - - type: object - properties: { rebranch_to: *id002 } - additionalProperties: true - required: [ rebranch_to ] - UseCaseOperation: - oneOf: - - type: object - properties: - op: { type: string, const: set_header } - title: { anyOf: [ *id002, { type: 'null' } ] } - primary_actor: { anyOf: [ *id002, { type: 'null' } ] } - secondary_actors: { anyOf: [ { type: array, items: *id002 }, { type: 'null' } ] } - summary: { anyOf: [ *id002, { type: 'null' } ] } - additionalProperties: false - required: [ op ] - - type: object - properties: - op: { type: string, const: add_element } - list: { $ref: '#/components/schemas/UseCaseListRef' } - after: { anyOf: [ *id002, { type: 'null' } ] } - element: { $ref: '#/components/schemas/UseCaseNewElement' } - additionalProperties: false - required: [ op, list, element ] - - type: object - properties: { op: { type: string, const: update_text }, id: *id002, text: *id002 } - additionalProperties: false - required: [ op, id, text ] - - type: object - properties: - op: { type: string, const: move_element } - id: *id002 - list: { anyOf: [ { $ref: '#/components/schemas/UseCaseListRef' }, { type: 'null' } ] } - after: { anyOf: [ *id002, { type: 'null' } ] } - additionalProperties: false - required: [ op, id ] - - type: object - properties: - op: { type: string, const: remove_element } - id: *id002 - dependents: { anyOf: [ { $ref: '#/components/schemas/UseCaseDependents' }, { type: 'null' } ] } - additionalProperties: false - required: [ op, id ] - - type: object - properties: { op: { type: string, const: add_branch }, branch: { $ref: '#/components/schemas/UseCaseNewBranch' } } - additionalProperties: false - required: [ op, branch ] - - type: object - properties: - op: { type: string, const: update_branch } - id: *id002 - title: { anyOf: [ *id002, { type: 'null' } ] } - kind: { anyOf: [ { type: string, enum: [ alternative, exception ] }, { type: 'null' } ] } - from_step: { anyOf: [ *id002, { type: 'null' } ] } - end: { anyOf: [ { $ref: '#/components/schemas/UseCaseBranchEnd' }, { type: 'null' } ] } - additionalProperties: false - required: [ op, id ] - - type: object - properties: { op: { type: string, const: remove_branch }, id: *id002 } - additionalProperties: false - required: [ op, id ] - UseCaseDiffSide: - type: object - properties: - list: { type: string, enum: [ preconditions, main, postconditions, branch, branches ] } - branch_id: *id002 - label: { anyOf: [ *id002, { type: 'null' } ] } - text: *id002 - from_step: *id002 - end: { $ref: '#/components/schemas/UseCaseBranchEnd' } - kind: { type: string, enum: [ alternative, exception ] } - additionalProperties: true - required: [ list, label, text ] - UseCaseElementDiff: - type: object - properties: - id: *id002 - kind: { type: string, enum: [ precondition, postcondition, step, branch ] } - changes: { type: array, items: { type: string, enum: [ added, removed, edited, moved, rebranched ] } } - old: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffSide' }, { type: 'null' } ] } - new: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffSide' }, { type: 'null' } ] } - text_diff: - type: array - items: - type: object - properties: { op: { type: string, enum: [ equal, delete, insert ] }, text: *id002 } - additionalProperties: true - required: [ op, text ] - additionalProperties: true - required: [ id, kind, changes, old, new ] - UseCaseDiffEnd: - type: object - properties: - version_id: { anyOf: [ *id003, { type: 'null' } ] } - version_number: { anyOf: [ *id003, { type: 'null' } ] } - draft_revision: { anyOf: [ *id003, { type: 'null' } ] } - additionalProperties: true - required: [ version_id, version_number, draft_revision ] - UseCaseDiff: - type: object - properties: - from: { anyOf: [ { $ref: '#/components/schemas/UseCaseDiffEnd' }, { type: 'null' } ] } - to: { $ref: '#/components/schemas/UseCaseDiffEnd' } - summary: - type: object - properties: - added: *id003 - removed: *id003 - edited: *id003 - moved: *id003 - rebranched: *id003 - additionalProperties: true - required: [ added, removed, edited, moved, rebranched ] - header: - type: object - additionalProperties: - type: object - properties: - old: &id011 { oneOf: [ *id002, { type: 'null' }, { type: array, items: *id002 } ] } - new: *id011 - additionalProperties: true - required: [ old, new ] - elements: { type: array, items: { $ref: '#/components/schemas/UseCaseElementDiff' } } - additionalProperties: true - required: [ from, to, summary, header, elements ] - UseCasePublishResponse: - type: object - properties: - version: { $ref: '#/components/schemas/use_cases_VersionView_response' } - removed_links: { type: array, items: { $ref: '#/components/schemas/use_cases_LinkView_response' } } - additionalProperties: true - required: [ version, removed_links ] - responses: - ApiError: - description: API error; use cases may include code and structured details. Guard/fairing errors can have an - empty body. - content: { application/json: { schema: { $ref: '#/components/schemas/ApiError' } } } -security: [ { cookieAuth: [] } ] diff --git a/docs/developer/openapi/components/responses.yaml b/docs/developer/openapi/components/responses.yaml new file mode 100644 index 00000000..bab0b3f6 --- /dev/null +++ b/docs/developer/openapi/components/responses.yaml @@ -0,0 +1,6 @@ +ApiError: + description: API error; use cases may include code and structured details. Guard/fairing errors can have an empty body. + content: + application/json: + schema: + $ref: ../schemas/common.yaml#/ApiError diff --git a/docs/developer/openapi/components/securitySchemes.yaml b/docs/developer/openapi/components/securitySchemes.yaml new file mode 100644 index 00000000..1a8b6ceb --- /dev/null +++ b/docs/developer/openapi/components/securitySchemes.yaml @@ -0,0 +1,16 @@ +cookieAuth: + type: apiKey + in: cookie + name: session + description: Rocket private session; __Host-session on secure deployments. Mutations require X-CSRF-Token. +bearerAuth: + type: http + scheme: bearer + description: >- + Personal API token or delegated OAuth access token; project membership, domain scopes and project + permissions apply. +mcpServiceAuth: + type: apiKey + in: header + name: X-Marreq-Mcp-Audit-Secret + description: Private service-to-service audit secret; never browser or delegated caller configuration. diff --git a/docs/developer/openapi/paths/admin.yaml b/docs/developer/openapi/paths/admin.yaml new file mode 100644 index 00000000..bcc829b3 --- /dev/null +++ b/docs/developer/openapi/paths/admin.yaml @@ -0,0 +1,368 @@ +/api/cache/stats: + get: + operationId: cache_stats + description: >- + Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator + only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/CacheStats}}} + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: Not an administrator, or CSRF check failed; body may be framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError +/api/cache/recommendations: + get: + operationId: cache_recommendations + description: >- + Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator + only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/CacheRecommendations}}} + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: Not an administrator, or CSRF check failed; body may be framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError +/api/cache/clear: + post: + operationId: cache_clear + description: >- + Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator + only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/CacheActionResult}}} + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: Not an administrator, or CSRF check failed; body may be framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf +/api/cache/cleanup: + post: + operationId: cache_cleanup + description: >- + Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator + only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/CacheCleanupResult}}} + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: Not an administrator, or CSRF check failed; body may be framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf +/api/cache/reset-counters: + post: + operationId: cache_reset_counters + description: >- + Audited against api/cache.rs, repository/cache/stats.rs and auth/guards/admin.rs. Session administrator + only; identical in Server and Cloud. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/CacheActionResult}}} + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: Not an administrator, or CSRF check failed; body may be framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf +/api/admin/backup/info: + get: + operationId: backup_info + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/BackupInfo}}} + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '410': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud + returns 410 because allows_database_backup is false. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin;allows_database_backup +/api/admin/backup: + post: + operationId: backup_create + parameters: + - in: query + name: attachments + schema: {type: boolean} + required: false + responses: + '200': + description: Server backup archive; Cloud returns 410. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/gzip: {schema: {type: string, format: binary}}} + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '410': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/backup.rs. Session instance administrator; success is available in Server mode, Cloud + returns 410 because allows_database_backup is false. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin;allows_database_backup + x-marreq-csrf: session-csrf +/api/admin/logs: + get: + operationId: logs_list + parameters: + - in: query + name: entity_type + schema: {type: string} + required: false + - in: query + name: action_type + schema: {type: string} + required: false + - in: query + name: since + schema: {type: string} + required: false + - in: query + name: until + schema: {type: string} + required: false + - in: query + name: entity_id + schema: {type: integer} + required: false + - in: query + name: user_id + schema: {type: integer} + required: false + - in: query + name: project_id + schema: {type: integer} + required: false + - in: query + name: limit + schema: {type: integer} + required: false + - in: query + name: offset + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/AdminLogList}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both variants. + Datetime filters are parsed server-side; pagination and top are clamped. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin +/api/admin/logs/stats: + get: + operationId: logs_stats + parameters: + - in: query + name: entity_type + schema: {type: string} + required: false + - in: query + name: action_type + schema: {type: string} + required: false + - in: query + name: since + schema: {type: string} + required: false + - in: query + name: until + schema: {type: string} + required: false + - in: query + name: user_id + schema: {type: integer} + required: false + - in: query + name: project_id + schema: {type: integer} + required: false + - in: query + name: top + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/LogStatsResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both variants. + Datetime filters are parsed server-side; pagination and top are clamped. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin +/api/admin/logs/export.json: + get: + operationId: logs_export_json + parameters: + - in: query + name: entity_type + schema: {type: string} + required: false + - in: query + name: action_type + schema: {type: string} + required: false + - in: query + name: since + schema: {type: string} + required: false + - in: query + name: until + schema: {type: string} + required: false + - in: query + name: entity_id + schema: {type: integer} + required: false + - in: query + name: user_id + schema: {type: integer} + required: false + - in: query + name: project_id + schema: {type: integer} + required: false + - in: query + name: limit + schema: {type: integer} + required: false + - in: query + name: offset + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/admin.yaml#/AdminLogList}}} + headers: {Content-Disposition: {required: true, schema: {type: string}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both variants. + Datetime filters are parsed server-side; pagination and top are clamped. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin +/api/admin/logs/cleanup: + post: + operationId: logs_cleanup + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {deleted: {type: integer}}, required: [deleted], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/logs.rs and services/log_service.rs. Session instance administrator only in both variants. + Datetime filters are parsed server-side; pagination and top are clamped. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {days: {type: integer, minimum: 1}}, required: [days], additionalProperties: true}} + x-marreq-authorization: instance-admin + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/attachments.yaml b/docs/developer/openapi/paths/attachments.yaml new file mode 100644 index 00000000..e47d6c7b --- /dev/null +++ b/docs/developer/openapi/paths/attachments.yaml @@ -0,0 +1,301 @@ +/api/projects/{project_id}/attachments: + get: + operationId: attachments_list + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: entity_type + required: true + schema: {type: string, enum: [requirement, verification]} + - in: query + name: entity_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/attachments.yaml#/AttachmentView}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-requirements-and-verifications-read + x-marreq-bearer-scopes: + - requirements:read + - verifications:read + post: + operationId: attachments_upload + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/attachments.yaml#/AttachmentView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '413': + description: Upload exceeds the file or project quota, or Rocket's request limit. The handler returns ApiError JSON; Rocket can reject an oversized form before the handler with a framework-generated body. + '415': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + multipart/form-data: {schema: {type: object, properties: {file: {type: string, format: binary}, entity_type: {type: string, enum: [requirement, verification]}, entity_id: {type: integer}}, required: [file, entity_type, entity_id], additionalProperties: true}} +/api/projects/{project_id}/attachments/{attachment_id}/download: + get: + operationId: attachments_download + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + '200': + description: Stored file served as a download using its detected media type. + headers: {Content-Disposition: {required: true, schema: {type: string}}, X-Content-Type-Options: {required: true, schema: {type: string, const: nosniff}}, Content-Security-Policy: {required: true, schema: {type: string, const: default-src 'none'; sandbox}}, Cache-Control: {required: true, schema: {type: string, const: 'private, no-cache'}}} + content: {'*/*': {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied +/api/projects/{project_id}/storage: + get: + operationId: attachments_get_storage + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/attachments.yaml#/AttachmentStorage}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied +/api/projects/{project_id}/storage/quota: + put: + operationId: attachments_set_quota + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/attachments.yaml#/AttachmentStorage}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin;not-archived + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {quota_mb: {anyOf: [{type: integer, minimum: 1, maximum: 1073741824}, {type: 'null'}]}}, additionalProperties: false}} +/api/projects/{project_id}/baselines/{baseline_id}/attachments: + get: + operationId: attachments_list_for_baseline + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/attachments.yaml#/AttachmentView}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read + x-marreq-bearer-scopes: + - baselines:read +/api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: + get: + operationId: attachments_download_for_baseline + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + '200': + description: Stored file served as a download using its detected media type. + headers: {Content-Disposition: {required: true, schema: {type: string}}, X-Content-Type-Options: {required: true, schema: {type: string, const: nosniff}}, Content-Security-Policy: {required: true, schema: {type: string, const: default-src 'none'; sandbox}}, Cache-Control: {required: true, schema: {type: string, const: 'private, no-cache'}}} + content: {'*/*': {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;delegated-baselines-read + x-marreq-bearer-scopes: + - baselines:read +/api/projects/{project_id}/attachments/{attachment_id}: + delete: + operationId: attachments_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: attachment_id + required: true + schema: {type: integer} + responses: + '204': + description: Attachment deleted; empty body. Baseline retention can keep the stored bytes. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '403': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + '503': + description: Guard, CSRF, storage configuration, or Rocket form validation failed; body may be empty or framework-generated. + description: >- + Audited api/attachments.rs, attachment_service.rs, auth/guards/project.rs and bearer.rs. Same routes and + wire types in Server and Cloud; configured storage limits remain instance policy. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;EditRequirements;not-archived;personal-token-or-session;delegated-denied + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/auth.yaml b/docs/developer/openapi/paths/auth.yaml new file mode 100644 index 00000000..9baf6a64 --- /dev/null +++ b/docs/developer/openapi/paths/auth.yaml @@ -0,0 +1,585 @@ +/api/auth/providers: + get: + operationId: auth_auth_providers + security: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/external_ProviderDiscovery_response}}} +/api/auth/external/{provider}/start: + get: + operationId: auth_auth_external_start + parameters: + - in: path + name: provider + required: true + schema: {type: string} + - in: query + name: return_to + schema: {type: string} + responses: + '303': + description: Redirect to the provider or validated local return path. + headers: {Location: {required: true, schema: {type: string}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: [] +/api/auth/external/{provider}/callback: + get: + operationId: auth_auth_external_callback + parameters: + - in: path + name: provider + required: true + schema: {type: string} + - in: query + name: code + schema: {type: string} + - in: query + name: state + schema: {type: string} + - in: query + name: error + schema: {type: string} + responses: + '303': + description: Redirect to the provider or validated local return path. + headers: {Location: {required: true, schema: {type: string}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + $ref: ../components/responses.yaml#/ApiError + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: [] + x-marreq-authorization: single-use-oauth-transaction;link-requires-original-session +/api/auth/identities: + get: + operationId: auth_auth_identities + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthIdentities}}} + '401': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: + - cookieAuth: [] +/api/auth/me: + get: + operationId: auth_auth_me + security: + - cookieAuth: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/entities_User_response}}} + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + description: API error + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/ApiError}}} + put: + operationId: auth_auth_update_me + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/entities_User_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + $ref: ../components/responses.yaml#/ApiError + '403': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and services/user_service.rs. Own profile only. Email changes require the current + password when configured; Cloud rejects email changes. Other profile changes work in both variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthProfileUpdate}} + x-marreq-csrf: session-csrf +/.well-known/oauth-authorization-server: + get: + operationId: oauth_authorization_server_metadata + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {issuer: {type: string}, authorization_endpoint: {type: string}, token_endpoint: {type: string}, registration_endpoint: {type: string}, response_types_supported: {type: array, items: {type: string}}, grant_types_supported: {type: array, items: {type: string}}, code_challenge_methods_supported: {type: array, items: {type: string}}, scopes_supported: {type: array, items: {type: string}}, token_endpoint_auth_methods_supported: {type: array, items: {type: string}}, authorization_response_iss_parameter_supported: {type: boolean}}, required: [issuer, authorization_endpoint, token_endpoint, registration_endpoint, response_types_supported, grant_types_supported, code_challenge_methods_supported, scopes_supported, token_endpoint_auth_methods_supported, authorization_response_iss_parameter_supported], additionalProperties: true}}} + description: See implementation and http-api-contract.md for validation rules. + security: [] +/.well-known/oauth-protected-resource/mcp: + get: + operationId: oauth_protected_resource_metadata_mcp + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {resource: {type: string}, authorization_servers: {type: array, items: {type: string}}, scopes_supported: {type: array, items: {type: string}}, bearer_methods_supported: {type: array, items: {type: string}}}, required: [resource, authorization_servers, scopes_supported, bearer_methods_supported], additionalProperties: true}}} + description: See implementation and http-api-contract.md for validation rules. + security: [] +/oauth/register: + post: + operationId: oauth_register + parameters: [] + responses: + '201': + description: Success + content: {application/json: {schema: {type: object, properties: {client_id: {type: string}, client_name: {type: string}, redirect_uris: {type: array, items: {type: string}}, token_endpoint_auth_method: {type: string, const: none}}, required: [client_id, client_name, redirect_uris, token_endpoint_auth_method], additionalProperties: true}}} + '400': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + '401': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '503': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + description: >- + Audited api/oauth.rs RegistrationRequest/register/OAuthProtocolError and auth/delegated.rs. Public PKCE + client registration, shared in Server and Cloud; OAuth errors are not ApiError envelopes. + security: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {client_name: {type: string}, redirect_uris: {type: array, items: {type: string}}}, required: [client_name, redirect_uris], additionalProperties: true}} + x-marreq-authorization: public-dynamic-client-registration;redirect-uri-policy;registration-rate-limit +/oauth/authorize: + get: + operationId: oauth_authorize_page + parameters: + - in: query + name: response_type + schema: {type: string} + required: true + - in: query + name: client_id + schema: {type: string} + required: true + - in: query + name: redirect_uri + schema: {type: string} + required: true + - in: query + name: scope + schema: {type: string} + required: true + - in: query + name: state + schema: {type: string} + required: true + - in: query + name: code_challenge + schema: {type: string} + required: true + - in: query + name: code_challenge_method + schema: {type: string} + required: true + - in: query + name: resource + schema: {type: string} + required: true + responses: + '200': + description: HTML consent form for an authenticated browser. + content: {text/html: {schema: {type: string}}} + '303': + description: 303 redirect to login or the validated OAuth redirect_uri. + headers: {Location: {required: true, schema: {type: string}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/oauth.rs authorize_page/checked_request. Trusted redirect errors and anonymous login return + 303; consent returns HTML. Unknown clients and invalid untrusted requests return API errors. Same in + both variants. + security: [] + x-marreq-authorization: validated-client-redirect-resource-pkce-scopes;optional-session-consent + post: + operationId: oauth_authorize_decision + parameters: [] + responses: + '303': + description: 303 redirect to login or the validated OAuth redirect_uri. + headers: {Location: {required: true, schema: {type: string}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/oauth.rs ConsentForm and authorize_decision. Form submissions use the session and + origin/CSRF fairing policy; both allow and deny redirect with OAuth state/issuer. Shared in both + variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/x-www-form-urlencoded: {schema: {type: object, properties: {client_id: {type: string}, redirect_uri: {type: string}, scope: {type: string}, state: {type: string}, code_challenge: {type: string}, resource: {type: string}, decision: {type: string}}, required: [client_id, redirect_uri, scope, state, code_challenge, resource, decision], additionalProperties: true}} + x-marreq-authorization: session;validated-client-redirect-resource-pkce-scopes;consent-decision + x-marreq-csrf: auth-origin-fallback +/oauth/token: + post: + operationId: oauth_token + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {access_token: {type: string}, token_type: {type: string, const: Bearer}, expires_in: {type: integer}, refresh_token: {type: string}, scope: {type: string}}, required: [access_token, token_type, expires_in, refresh_token, scope], additionalProperties: true}}} + '400': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + '401': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '503': + description: OAuth protocol error. + content: {application/json: {schema: {type: object, properties: {error: {type: string}}, required: [error], additionalProperties: true}}} + description: >- + Audited api/oauth.rs TokenForm/token and auth/delegated.rs TokenResponse. Public form endpoint with + client/resource/code/refresh checks; protocol errors use an error string. Shared in both variants. + security: [] + requestBody: + required: true + content: + application/x-www-form-urlencoded: {schema: {$ref: ../schemas/auth.yaml#/OAuthTokenRequest}} + x-marreq-authorization: public-pkce-code-or-refresh-token;client-resource-binding;single-use-rotation +/api/auth/csrf: + get: + operationId: auth_auth_csrf + security: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {csrf_token: {type: string}}, required: [csrf_token], additionalProperties: true}}} +/api/auth/identities/{identity_id}: + delete: + operationId: auth_auth_identity_delete + parameters: + - in: path + name: identity_id + required: true + schema: {type: integer} + responses: + '204': + description: Owned identity removed; no body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + $ref: ../components/responses.yaml#/ApiError + '403': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: + - cookieAuth: [] + x-marreq-csrf: session-csrf +/api/auth/register: + post: + operationId: auth_public_auth_register + x-marreq-variants: + - cloud + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status], additionalProperties: true}}} + '201': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, + preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {username: {type: string}, name: {type: string}, email: {type: string}, password: {type: string}}, required: [username, name, email, password], additionalProperties: true}} +/api/auth/login: + post: + operationId: auth_auth_login + parameters: [] + responses: + '200': + description: Authenticated session established + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, user: {$ref: ../schemas/auth.yaml#/entities_User_response}}, required: [status, user]}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '403': + description: CSRF failure; body may be framework generated + '410': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Rocket JSON validation failure + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Route inventory only. Request/response serialization, status codes and authorization require audit + before compatibility can be established. + security: [] + x-marreq-csrf: auth-origin-fallback + requestBody: + required: true + content: + application/json: {schema: {type: object, required: [username, password], properties: {username: {type: string}, password: {type: string}}}} +/api/auth/external/{provider}/link: + post: + operationId: auth_auth_external_link_start + parameters: + - in: path + name: provider + required: true + schema: {type: string} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {authorization_url: {type: string}}, required: [authorization_url], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + $ref: ../components/responses.yaml#/ApiError + '403': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '404': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {description: Any JSON value is accepted; an object return_to string overrides the default /settings/account., properties: {return_to: {type: string}}}} + x-marreq-csrf: session-csrf +/api/auth/verify-email: + get: + operationId: auth_public_auth_verify_email + x-marreq-variants: + - cloud + parameters: + - in: query + name: token + required: true + schema: {type: string} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, + preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] +/api/auth/logout: + post: + operationId: auth_auth_logout + parameters: [] + responses: + '200': + description: Session and CSRF cookies cleared + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status]}}} + '403': + description: CSRF failure; body may be framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Route inventory only. Request/response serialization, status codes and authorization require audit + before compatibility can be established. + security: [] + x-marreq-csrf: auth-origin-fallback + requestBody: + required: false + description: Optional ignored body; the SPA sends an empty JSON object. + content: + application/json: {schema: {}} +/api/auth/forgot-password: + post: + operationId: auth_public_auth_forgot_password + x-marreq-variants: + - cloud + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, + preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {email: {type: string}}, required: [email], additionalProperties: true}} +/api/auth/change-password: + post: + operationId: auth_auth_change_password + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + $ref: ../components/responses.yaml#/ApiError + '403': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '422': + description: Authentication or CSRF failure; body may be framework-generated or empty. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/auth.rs and models/forms.rs. Session-scoped authentication; identical routes in Server and + Cloud. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthChangePassword}} + x-marreq-csrf: session-csrf +/api/oauth/grants: + get: + operationId: oauth_grants + parameters: [] + responses: + '200': + description: Connected applications + content: {application/json: {schema: {type: object, required: [grants], properties: {grants: {type: array, items: {type: object, required: [id, application_name, scopes, created_at, last_used_at], properties: {id: {type: integer}, application_name: {type: string}, scopes: {type: array, items: {type: string}}, created_at: {type: string}, last_used_at: {type: [string, 'null']}}, additionalProperties: true}}}, additionalProperties: true}}} + '401': + description: Missing or expired session; guard response may be empty. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants only, + identical in Server and Cloud; no Bearer authentication. + security: + - cookieAuth: [] +/api/oauth/grants/{id}: + delete: + operationId: oauth_revoke_grant + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Owned grant revoked; empty body. + '401': + description: Missing or expired session; guard response may be empty. + '403': + description: CSRF failure; body may be framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/oauth.rs grants/revoke_grant and models/entities.rs OAuthGrant. Owned session grants only, + identical in Server and Cloud; no Bearer authentication. + security: + - cookieAuth: [] + x-marreq-csrf: session-csrf +/api/auth/reset-password: + post: + operationId: auth_public_auth_reset_password + x-marreq-variants: + - cloud + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}}, required: [status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Cloud-only public account flow. Registration returns 201 for creation or 200 for an existing email, + preventing enumeration. Forgot-password always reports success. CSRF fairing rules still apply. + security: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {token: {type: string}, new_password: {type: string}}, required: [token, new_password], additionalProperties: true}} diff --git a/docs/developer/openapi/paths/baselines.yaml b/docs/developer/openapi/paths/baselines.yaml new file mode 100644 index 00000000..62a1c96d --- /dev/null +++ b/docs/developer/openapi/paths/baselines.yaml @@ -0,0 +1,368 @@ +/api/projects/{project_id}/baselines: + get: + operationId: baselines_list + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/baselines.yaml#/Baseline}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements + post: + operationId: baselines_create + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore or hyphen. Reuse with a different payload returns 409. + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/Baseline}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {name: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, saved_view_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [name], additionalProperties: true}} + x-marreq-bearer-scopes: + - baselines:write + x-marreq-authorization: project-membership;EditRequirements;visible-saved-view;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/baselines/{baseline_id}: + get: + operationId: baselines_get + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/Baseline}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/{baseline_id}/requirements: + get: + operationId: baselines_get_requirements + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/requirements.yaml#/Requirement}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/{baseline_id}/traceability: + get: + operationId: baselines_get_traceability + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/baselines.yaml#/BaselineTraceability}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/{baseline_id}/verifications: + get: + operationId: baselines_get_verifications + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/baselines.yaml#/BaselineVerification}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/diff: + get: + operationId: baselines_diff_baselines + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: baseline_a + schema: {type: integer} + required: true + - in: query + name: baseline_b + schema: {type: integer} + required: true + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/BaselineDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/baselines.rs, BaselineService and baseline models. Immutable snapshots with nullable source + view metadata. Both variants enforce project boundaries. Optional Idempotency-Key applies to create. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: + get: + operationId: requirement_diff_diff_baseline_vs_current + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/RequirementDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both versions/snapshots + must belong to the requested entity and project. Optional SingleValueDiff/label fields are omitted, not + null. Shared wire representation in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: + get: + operationId: verification_diff_diff_baseline_vs_current + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/VerificationVersionDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both versions/snapshots + must belong to the requested entity and project. Optional SingleValueDiff/label fields are omitted, not + null. Shared wire representation in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + - baselines:read + x-marreq-authorization: project-membership;ViewRequirements diff --git a/docs/developer/openapi/paths/catalog.yaml b/docs/developer/openapi/paths/catalog.yaml new file mode 100644 index 00000000..952a7c3d --- /dev/null +++ b/docs/developer/openapi/paths/catalog.yaml @@ -0,0 +1,528 @@ +/api/applicability: + get: + operationId: applicability_list + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/catalog.yaml#/Applicability}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations + are rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ViewRequirements + post: + operationId: applicability_create + parameters: [] + responses: + '201': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations + are rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewApplicability}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/applicability/{id}: + put: + operationId: applicability_update + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string}, message: {type: string}}, required: [status, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations + are rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewApplicability}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: applicability_delete + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/applicability.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; + reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations + are rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/categories: + get: + operationId: categories_list + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/catalog.yaml#/Category}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ViewRequirements + post: + operationId: categories_create + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewCategory}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/categories/{id}: + put: + operationId: categories_update + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string}, message: {type: string}}, required: [status, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewCategory}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: categories_delete + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/categories.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/status: + get: + operationId: status_list_requirement_statuses + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/catalog.yaml#/RequirementStatus}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ViewRequirements + post: + operationId: status_create_requirement_status + parameters: [] + responses: + '201': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewRequirementStatus}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/status/{id}: + put: + operationId: status_update_requirement_status + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewRequirementStatus}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: status_delete_requirement_status + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire shape; reads + filter by ViewRequirements, mutations require ManageProjectConfiguration. System status mutations are + rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/verification-status: + get: + operationId: verification_status_list_verification_statuses + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/catalog.yaml#/VerificationStatus}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ViewRequirements + post: + operationId: verification_status_create_verification_status + parameters: [] + responses: + '201': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewVerificationStatus}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/verification-status/{id}: + put: + operationId: verification_status_update_verification_status + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/catalog.yaml#/NewVerificationStatus}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: verification_status_delete_verification_status + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_status.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/mcp.yaml b/docs/developer/openapi/paths/mcp.yaml new file mode 100644 index 00000000..599558d2 --- /dev/null +++ b/docs/developer/openapi/paths/mcp.yaml @@ -0,0 +1,87 @@ +/api/mcp/principal: + get: + operationId: mcp_principal + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/mcp.yaml#/McpPrincipalResponse}}} + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. +/api/mcp/audit: + post: + operationId: mcp_audit + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user credential; + stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are enforced. Shared + in Server and Cloud. + security: + - bearerAuth: [] + x-marreq-authentication-note: Personal API tokens only; delegated OAuth and sessions rejected. + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/mcp.yaml#/McpAuditRequest}} + x-marreq-authorization: personal-api-token-only;project-membership + x-marreq-csrf: session-csrf +/api/mcp/internal/audit: + post: + operationId: mcp_internal_audit + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/mcp.rs and McpAuditAuth. Internal audit requires both service secret and user credential; + stdio rejects delegated OAuth and sessions. Allowed tool names and summary limits are enforced. Shared + in Server and Cloud. + security: + - bearerAuth: [] + mcpServiceAuth: [] + - cookieAuth: [] + mcpServiceAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/mcp.yaml#/McpAuditRequest}} + x-marreq-authorization: internal-mcp-service-secret;authenticated-user;project-membership + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/notifications.yaml b/docs/developer/openapi/paths/notifications.yaml new file mode 100644 index 00000000..4cbd8376 --- /dev/null +++ b/docs/developer/openapi/paths/notifications.yaml @@ -0,0 +1,223 @@ +/api/notifications: + get: + operationId: notifications_list + parameters: + - in: query + name: unread_only + schema: {type: boolean} + required: false + - in: query + name: limit + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/Notification}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned +/api/notifications/{id}/read: + patch: + operationId: notifications_mark_read + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {success: {type: boolean}}, required: [success], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf +/api/notifications/read-all: + post: + operationId: notifications_mark_all_read + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {count: {type: integer}}, required: [count], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf +/api/notifications/preferences: + get: + operationId: notifications_get_preferences + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/NotificationPreference}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned +/api/notifications/preferences/{project_id}: + put: + operationId: notifications_set_preference + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {notify_in_app: {anyOf: [{type: boolean}, {type: 'null'}]}, notify_email: {anyOf: [{type: boolean}, {type: 'null'}]}}, required: [], additionalProperties: true}} + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf + delete: + operationId: notifications_delete_preference + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned + x-marreq-csrf: session-csrf +/api/notifications/unread-count: + get: + operationId: notifications_unread_count + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {count: {type: integer}}, required: [count], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/notifications.rs, NotificationService and notification models. Session-only, scoped to the + current user in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-owned diff --git a/docs/developer/openapi/paths/project-settings.yaml b/docs/developer/openapi/paths/project-settings.yaml new file mode 100644 index 00000000..0fdd2274 --- /dev/null +++ b/docs/developer/openapi/paths/project-settings.yaml @@ -0,0 +1,344 @@ +/api/projects/{project_id}/custom_fields: + get: + operationId: custom_fields_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/CustomFieldDefinition}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field validation + apply in both variants; enum_values is arbitrary stored JSON or null. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements + post: + operationId: custom_fields_create + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field validation + apply in both variants; enum_values is arbitrary stored JSON or null. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/projects.yaml#/CustomFieldDefinitionPayload}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/custom_fields/{field_id}: + put: + operationId: custom_fields_update + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: field_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string}, message: {type: string}}, required: [status, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field validation + apply in both variants; enum_values is arbitrary stored JSON or null. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/projects.yaml#/CustomFieldDefinitionPayload}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: custom_fields_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: field_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string}, message: {type: string}}, required: [status, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/custom_fields.rs, models and CustomFieldService. Project boundaries and field validation + apply in both variants; enum_values is arbitrary stored JSON or null. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageCustomFields;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/saved_views: + get: + operationId: saved_views_list + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/SavedView}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as serde_json::Value + after service validation. Private visibility, ownership, baseline locking and archived-project rules are + enforced in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements;scoped-requirements-read + post: + operationId: saved_views_create + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/SavedView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as serde_json::Value + after service validation. Private visibility, ownership, baseline locking and archived-project rules are + enforced in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/projects.yaml#/SavedViewPayload}} + x-marreq-authorization: >- + project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf +/api/projects/{project_id}/saved_views/{view_id}: + get: + operationId: saved_views_get + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/SavedView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as serde_json::Value + after service validation. Private visibility, ownership, baseline locking and archived-project rules are + enforced in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: project-membership;ViewRequirements;personal-token-or-session;delegated-denied;visible-owner-or-shared + patch: + operationId: saved_views_update + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/SavedView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as serde_json::Value + after service validation. Private visibility, ownership, baseline locking and archived-project rules are + enforced in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/projects.yaml#/SavedViewPayload}} + x-marreq-authorization: >- + project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf + delete: + operationId: saved_views_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: view_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/saved_views.rs and SavedViewService/models. JSON definition is stored as serde_json::Value + after service validation. Private visibility, ownership, baseline locking and archived-project rules are + enforced in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: >- + project-membership;ViewRequirements;personal-token-or-session;delegated-denied;not-archived;owner-or-project-admin;unlocked + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/projects.yaml b/docs/developer/openapi/paths/projects.yaml new file mode 100644 index 00000000..a0c6b0b7 --- /dev/null +++ b/docs/developer/openapi/paths/projects.yaml @@ -0,0 +1,636 @@ +/api/projects/{project_id}/catalog: + get: + operationId: catalog_get + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {categories: {type: array, items: {$ref: ../schemas/catalog.yaml#/Category}}, applicability: {type: array, items: {$ref: ../schemas/catalog.yaml#/Applicability}}, requirement_statuses: {type: array, items: {$ref: ../schemas/catalog.yaml#/RequirementStatus}}, verification_statuses: {type: array, items: {$ref: ../schemas/catalog.yaml#/VerificationStatus}}, verification_methods: {type: array, items: {$ref: ../schemas/verifications.yaml#/VerificationMethod}}, custom_fields: {type: array, items: {$ref: ../schemas/projects.yaml#/CustomFieldDefinition}}}, required: [categories, applicability, requirement_statuses, verification_statuses, verification_methods, custom_fields], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: Audited api/catalog.rs repository serializers. Project-scoped lookup arrays in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/home/attention: + get: + operationId: home_attention + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {items: {type: array, items: {$ref: ../schemas/projects.yaml#/AttentionItem}}, approvals: {type: integer}, reviews: {type: integer}, suspect_links: {type: integer}, notifications: {type: integer}}, required: [items, approvals, reviews, suspect_links, notifications], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/home.rs AttentionItem/AttentionResponse and ProjectsRead guard. Both variants; at most 50 + attention items, nullable entity fields are serialized explicitly. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - projects:read + x-marreq-authorization: current-project-membership;token-project-scope +/api/search: + get: + operationId: home_search + parameters: + - in: query + name: q + schema: {type: string} + required: true + - in: query + name: limit + schema: {type: integer, minimum: 0} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {type: object, properties: {kind: {type: string, enum: [requirement, use_case]}, project_id: {type: integer}, requirement_id: {anyOf: [{type: integer}, {type: 'null'}]}, use_case_id: {anyOf: [{type: integer}, {type: 'null'}]}, reference_code: {type: string}, title: {type: string}}, required: [kind, project_id, requirement_id, use_case_id, reference_code, title], additionalProperties: true}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/home.rs SearchHit and search. Session-only in both variants; limit is clamped, short queries + yield an empty array. + security: + - cookieAuth: [] + x-marreq-authorization: session;ViewRequirements +/api/projects/{project_id}/me/permissions: + get: + operationId: members_get_my_permissions + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/EffectivePermissions}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - projects:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/members: + get: + operationId: members_list_members + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/MemberResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/members/{user_id}: + put: + operationId: members_set_member_role + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/MemberResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {role: {type: integer, minimum: 1, maximum: 4}}, required: [role], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: members_remove_member + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/reviewers: + get: + operationId: members_list_project_reviewers + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/ProjectReviewersResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - projects:read + x-marreq-authorization: project-membership;ViewRequirements + put: + operationId: members_put_project_reviewers + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/ProjectReviewersResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/members.rs, permissions.rs and auth/guards/project.rs. Both variants enforce current + membership. Reviewer writes require project members; the last project admin cannot be removed. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/projects.yaml#/ProjectReviewersResponse}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ManageProjectMembers;not-archived + x-marreq-csrf: session-csrf +/api/meta/deployment: + get: + operationId: meta_deployment_info + security: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/DeploymentInfo}}} +/api/meta/build: + get: + operationId: meta_build_info + security: [] + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/BuildInfo}}} +/api/projects: + post: + operationId: projects_create + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {id: {type: integer}, name: {type: string}, slug: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, group_id: {anyOf: [{type: integer}, {type: 'null'}]}, project_base_path: {type: string}, status: {type: string, const: ok}}, required: [id, name, slug, description, group_id, project_base_path, status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {name: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, group_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [name], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects + x-marreq-csrf: session-csrf + get: + operationId: projects_session_list_for_session + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/Project}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects_session.rs and models::Project. Admin all-project view, otherwise current + memberships; bearer project scope narrows the list. Shared in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - projects:read + x-marreq-authorization: current-project-membership;token-project-scope +/api/projects/{project_id}: + patch: + operationId: projects_update + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {id: {type: integer}, name: {type: string}, slug: {type: string}, project_base_path: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, creation_date: {anyOf: [{type: string}, {type: 'null'}]}, update_date: {anyOf: [{type: string}, {type: 'null'}]}, owner_id: {anyOf: [{type: integer}, {type: 'null'}]}, group_id: {anyOf: [{type: integer}, {type: 'null'}]}, status: {$ref: ../schemas/catalog.yaml#/ProjectStatus}}, required: [id, name, slug, project_base_path, description, creation_date, update_date, owner_id, group_id, status], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {name: {anyOf: [{type: string}, {type: 'null'}]}, description: {anyOf: [{type: string}, {type: 'null'}]}, status: {anyOf: [{$ref: ../schemas/catalog.yaml#/ProjectStatus}, {type: 'null'}]}, owner_id: {anyOf: [{type: integer}, {type: 'null'}]}, group_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [], additionalProperties: false}} + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjectConfiguration;group-ManageProjects;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: projects_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {confirm_slug: {type: string}}, required: [confirm_slug], additionalProperties: false}} + x-marreq-authorization: session;project-owner-or-instance-admin;confirm-slug + x-marreq-csrf: session-csrf +/api/projects/{project_id}/archive: + post: + operationId: projects_archive + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/Project}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: + - cookieAuth: [] + x-marreq-authorization: session;project-owner-or-instance-admin + x-marreq-csrf: session-csrf +/api/projects/{project_id}/unarchive: + post: + operationId: projects_unarchive + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/projects.yaml#/Project}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects.rs and ProjectService. Shared wire types in both variants; project and group + permissions, ownership, archive status and strict PATCH field handling apply. + security: + - cookieAuth: [] + x-marreq-authorization: session;project-owner-or-instance-admin + x-marreq-csrf: session-csrf +/api/project-from-path/{slug}: + get: + operationId: projects_session_project_from_path + parameters: + - in: path + name: slug + required: true + schema: {type: string} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {id: {type: integer}, name: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, slug: {type: string}, route_slug: {type: string}}, required: [id, name, description, slug, route_slug], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/projects_session.rs project_from_path. Session-only slug lookup with membership checks, + shared in both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session;project-membership-or-instance-admin +/api/projects/{project_id}/close_out: + get: + operationId: verification_control_get_project_close_out + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, additionalProperties: {$ref: ../schemas/traceability.yaml#/RequirementCloseOutView}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs are + cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/dashboard: + get: + operationId: dashboard_dashboard_json + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {user: {$ref: ../schemas/auth.yaml#/entities_User_response}, projects: {type: array, items: {$ref: ../schemas/projects.yaml#/DashboardProject}}, projects_count: {type: integer}, selected_project_id: {anyOf: [{type: integer}, {type: 'null'}]}, selected_project_slug: {anyOf: [{type: string}, {type: 'null'}]}, csrf_token: {type: string}}, required: [user, projects, projects_count, selected_project_id, selected_project_slug, csrf_token], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/dashboard.rs and routes/helpers.rs decorate_projects_for_listing. Project cards include the + exact decoration keys; nulls reflect absent selection/metadata. Both variants share this session + payload. + security: + - cookieAuth: [] + x-marreq-authorization: session-user-project-memberships diff --git a/docs/developer/openapi/paths/reports.yaml b/docs/developer/openapi/paths/reports.yaml new file mode 100644 index 00000000..a6c2673b --- /dev/null +++ b/docs/developer/openapi/paths/reports.yaml @@ -0,0 +1,239 @@ +/api/projects/{project_id}/reports/types: + get: + operationId: reports_list_types + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {type: object, properties: {key: {type: string}, title: {type: string}, description: {type: string}, formats: {type: array, items: {type: string, enum: [pdf, odt]}}, sections: {type: array, items: {$ref: ../schemas/reports.yaml#/ReportSectionSpec}}, default_definition: {$ref: ../schemas/reports.yaml#/ReportDefinition}}, required: [key, title, description, formats, sections, default_definition], additionalProperties: true}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/report_templates: + get: + operationId: reports_list_templates + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/reports.yaml#/ReportTemplateView}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template + post: + operationId: reports_create_template + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/reports.yaml#/ReportTemplateView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/reports.yaml#/ReportTemplateInput}} + x-marreq-authorization: >- + personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf +/api/projects/{project_id}/report_templates/{template_id}: + patch: + operationId: reports_update_template + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: template_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/reports.yaml#/ReportTemplateView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/reports.yaml#/ReportTemplateInput}} + x-marreq-authorization: >- + personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf + delete: + operationId: reports_delete_template + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: template_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: >- + personal-token-or-session;delegated-denied;project-membership;ViewRequirements;not-archived;template-owner-or-admin + x-marreq-csrf: session-csrf +/api/projects/{project_id}/reports/{file}: + post: + operationId: reports_generate + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: file + required: true + schema: {type: string} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/pdf: {schema: {type: string, format: binary}}, application/vnd.oasis.opendocument.text: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/reports.rs, reports/definition.rs and reports/sections.rs. Session/personal token; delegated + OAuth denied. Both variants enforce project view permission, private-template visibility, owner/admin + mutation rules and archived-project write denial. Definition defaults and optional section options + follow Serde. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: false + content: + application/json: {schema: {type: object, properties: {template_id: {anyOf: [{type: integer}, {type: 'null'}]}, definition: {anyOf: [{$ref: ../schemas/reports.yaml#/ReportDefinitionInput}, {type: 'null'}]}}, required: [], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements;visible-template + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/requirement-history.yaml b/docs/developer/openapi/paths/requirement-history.yaml new file mode 100644 index 00000000..209c6660 --- /dev/null +++ b/docs/developer/openapi/paths/requirement-history.yaml @@ -0,0 +1,390 @@ +/api/projects/{project_id}/requirements/{id}/activity: + get: + operationId: activity_requirement_activity_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/common.yaml#/EntityActivityItem}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, permission and + project-boundary rules in Server and Cloud. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{requirement_id}/comments: + get: + operationId: comments_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + - in: query + name: version_id + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/common.yaml#/CommentResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements + post: + operationId: comments_create_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore or hyphen. Reuse with a different payload returns 409. + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/CommentResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {body: {type: string}, requirement_version_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [body], additionalProperties: true}} + x-marreq-bearer-scopes: + - requirements:write + x-marreq-authorization: project-membership;EditRequirements;not-archived;approved-version-comment-lock-policy + x-marreq-csrf: session-csrf +/api/requirements/{requirement_id}/comments: + get: + operationId: comments_list + parameters: + - in: path + name: requirement_id + required: true + schema: {type: integer} + - in: query + name: version_id + schema: {type: integer} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/common.yaml#/CommentResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: + - cookieAuth: [] + x-marreq-authorization: session;ViewRequirements + post: + operationId: comments_create + parameters: + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/CommentResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/comments.rs and CommentService. Immutable comments, nullable version binding, author + display-name fallback. Scoped create supports idempotency; legacy route uses session only. Shared in + both variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {body: {type: string}, requirement_version_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [body], additionalProperties: true}} + x-marreq-authorization: session;EditRequirements;not-archived;approved-version-comment-lock-policy + x-marreq-csrf: session-csrf +/api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: + get: + operationId: requirement_diff_diff_versions_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: v1 + required: true + schema: {type: integer} + - in: path + name: v2 + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/RequirementDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both versions/snapshots + must belong to the requested entity and project. Optional SingleValueDiff/label fields are omitted, not + null. Shared wire representation in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{id}/versions: + get: + operationId: requirements_list_versions_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/requirements.yaml#/RequirementVersion}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: + get: + operationId: requirements_get_version_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/requirements.yaml#/RequirementVersionDetail}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: + put: + operationId: requirements_set_version_approval_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: req_id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/requirements.yaml#/RequirementVersion}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {state: {type: string}}, required: [state], additionalProperties: true}} + x-marreq-bearer-scopes: + - requirements:approve + x-marreq-authorization: project-membership;project-reviewer;not-archived + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/requirements.yaml b/docs/developer/openapi/paths/requirements.yaml new file mode 100644 index 00000000..995dec04 --- /dev/null +++ b/docs/developer/openapi/paths/requirements.yaml @@ -0,0 +1,563 @@ +/api/projects/{project_id}/requirements: + get: + operationId: requirements_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: approval_state + schema: {type: string} + required: false + - in: query + name: q + schema: {type: string} + required: false + - in: query + name: sort_column + schema: {type: string} + required: false + - in: query + name: sort_dir + schema: {type: string} + required: false + - in: query + name: status_id + schema: {type: integer} + required: false + - in: query + name: category_id + schema: {type: integer} + required: false + - in: query + name: view_id + schema: {type: integer} + required: false + - in: query + name: has_tests + schema: {type: boolean} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/requirements.yaml#/RequirementListRow}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements + post: + operationId: requirements_create_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore or hyphen. Reuse with a different payload returns 409. + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/requirements.yaml#/RequirementCreateRequest}} + x-marreq-bearer-scopes: + - requirements:write + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/requirements/{id}: + delete: + operationId: requirements_delete + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + x-marreq-authorization: session;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/requirements/{id}: + get: + operationId: requirements_get_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/requirements.yaml#/RequirementWithTraceSummary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements + patch: + operationId: requirements_patch_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {success: {type: boolean}, message: {type: string}}, required: [success, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/requirements.yaml#/RequirementPatch}} + x-marreq-bearer-scopes: + - requirements:write + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/requirements/{id}/impacted_tests: + get: + operationId: requirements_get_impacted_tests_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/verifications.yaml#/Verification}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/semantic_search: + get: + operationId: semantic_search_semantic_search + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: q + schema: {type: string} + required: true + - in: query + name: k + schema: {type: integer, minimum: 0} + required: false + - in: query + name: status_filter + schema: {type: integer} + required: false + - in: query + name: category_filter + schema: {type: integer} + required: false + - in: query + name: applicability_filter + schema: {type: integer} + required: false + - in: query + name: verification_filter + schema: {type: integer} + required: false + - in: query + name: debug + schema: {type: boolean} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {oneOf: [{type: object, properties: {enabled: {type: boolean, const: false}, message: {type: string}, results: {type: array, items: {$ref: ../schemas/requirements.yaml#/SemanticSearchResult}, maxItems: 0}, total: {type: integer, const: 0}}, required: [enabled, message, results, total], additionalProperties: true}, {type: object, properties: {enabled: {type: boolean, const: true}, results: {type: array, items: {$ref: ../schemas/requirements.yaml#/SemanticSearchResult}}, query: {type: string}, total: {type: integer}, timing_ms: {type: integer}}, required: [enabled, results, query, total], additionalProperties: true}]}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/semantic_search.rs and models/semantic_search.rs. Disabled and enabled payloads differ + explicitly; timing_ms and rank details are optional. Both variants use runtime embedding configuration. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership +/api/projects/{project_id}/requirements/ask: + post: + operationId: semantic_search_ask + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/requirements.yaml#/RagAnswerResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/semantic_search.rs ask and RagAnswerResponse. RAG-disabled state returns 400, + provider/repository failures map to API errors. Shared in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {query: {type: string}, k: {anyOf: [{type: integer, minimum: 0}, {type: 'null'}]}, status_filter: {anyOf: [{type: integer}, {type: 'null'}]}, category_filter: {anyOf: [{type: integer}, {type: 'null'}]}, applicability_filter: {anyOf: [{type: integer}, {type: 'null'}]}, verification_filter: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [query], additionalProperties: true}} + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership + x-marreq-csrf: session-csrf +/api/projects/{project_id}/requirements/index_status: + get: + operationId: semantic_search_index_status + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + default: + description: Wire behavior has not yet been verified. + x-marreq-unverified: true + description: Wire contract pending audit; required consumers fail verification. + security: + - cookieAuth: [] +/api/projects/{project_id}/requirements/{id}/trace_up: + get: + operationId: traceability_trace_up + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {parent: {anyOf: [{$ref: ../schemas/requirements.yaml#/Requirement}, {type: 'null'}]}, parents: {type: array, items: {$ref: ../schemas/requirements.yaml#/Requirement}}}, required: [parent, parents], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses retain + nullable first parent, complete parent arrays and explicit no_change result, identically in both + variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{id}/trace_down: + get: + operationId: traceability_trace_down + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {child_requirements: {type: array, items: {$ref: ../schemas/requirements.yaml#/Requirement}}, linked_tests: {type: array, items: {$ref: ../schemas/verifications.yaml#/Verification}}}, required: [child_requirements, linked_tests], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses retain + nullable first parent, complete parent arrays and explicit no_change result, identically in both + variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + - verifications:read + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{requirement_id}/close_out: + get: + operationId: verification_control_get_requirement_close_out + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/RequirementCloseOutView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs are + cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirements/{requirement_id}/compliance: + put: + operationId: verification_control_put_requirement_compliance + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/RequirementCloseOutView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs are + cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {compliance: {anyOf: [{type: string}, {type: 'null'}]}, note: {anyOf: [{type: string}, {type: 'null'}]}}, required: [], additionalProperties: false}} + x-marreq-bearer-scopes: + - requirements:write + x-marreq-authorization: project-membership;EditRequirements;project-reviewer;not-archived + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/traceability.yaml b/docs/developer/openapi/paths/traceability.yaml new file mode 100644 index 00000000..7dbb6819 --- /dev/null +++ b/docs/developer/openapi/paths/traceability.yaml @@ -0,0 +1,369 @@ +/api/projects/{project_id}/dsm: + get: + operationId: dsm_get_dsm + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: link_types + schema: {type: string} + required: false + - in: query + name: category_id + schema: {type: integer} + required: false + - in: query + name: root_id + schema: {type: integer} + required: false + - in: query + name: order + schema: {type: string, enum: [hierarchy, partition]} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/Dsm}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/dsm.rs and dsm_service.rs output structs. Comma-separated link types, optional project + category/root, hierarchy or partition order; numeric indices and loop paths are preserved. Shared in + both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - requirements:read + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/matrix: + get: + operationId: matrix_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/traceability.yaml#/MatrixLink}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every entity + belongs to the project; both variants share the shape. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirement-version-links: + post: + operationId: requirement_version_links_create + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore or hyphen. Reuse with a different payload returns 409. + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/RequirementVersionLink}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirement_version_links.rs, models and RequirementService. Project-bound source/target + versions; optional Idempotency-Key is supported on create, reused payload mismatches return 409. Both + variants share this contract. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {source_version_id: {type: integer}, target_version_id: {type: integer}, link_type: {type: string}, rationale: {anyOf: [{type: string}, {type: 'null'}]}, metadata: {anyOf: [{}, {type: 'null'}]}}, required: [source_version_id, target_version_id, link_type], additionalProperties: true}} + x-marreq-bearer-scopes: + - traceability:write + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf + get: + operationId: requirement_version_links_list + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: source_version_id + schema: {type: integer} + required: false + - in: query + name: target_version_id + schema: {type: integer} + required: false + - in: query + name: link_type + schema: {type: string} + required: false + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/traceability.yaml#/RequirementVersionLink}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirement_version_links.rs, models and RequirementService. Project-bound source/target + versions; optional Idempotency-Key is supported on create, reused payload mismatches return 409. Both + variants share this contract. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/requirement-version-links/{link_id}: + delete: + operationId: requirement_version_links_delete + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/RequirementVersionLink}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirement_version_links.rs, models and RequirementService. Project-bound source/target + versions; optional Idempotency-Key is supported on create, reused payload mismatches return 409. Both + variants share this contract. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:write + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/requirement-version-links/link-types: + get: + operationId: requirement_version_links_link_types + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {link_types: {type: array, items: {type: string}}}, required: [link_types], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirement_version_links.rs, models and RequirementService. Project-bound source/target + versions; optional Idempotency-Key is supported on create, reused payload mismatches return 409. Both + variants share this contract. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:read + x-marreq-authorization: project-membership;project-access +/api/projects/{project_id}/coverage_report: + get: + operationId: traceability_coverage_report + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {requirements_without_tests: {type: array, items: {type: integer}}, tests_without_requirements: {type: array, items: {type: integer}}, suspect_links: {type: array, items: {type: object, properties: {req_id: {type: integer}, verification_id: {type: integer}}, required: [req_id, verification_id], additionalProperties: true}}}, required: [requirements_without_tests, tests_without_requirements, suspect_links], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses retain + nullable first parent, complete parent arrays and explicit no_change result, identically in both + variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements +/api/traceability/clear_suspect: + post: + operationId: traceability_clear_suspect + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, enum: [ok, no_change]}, cleared: {type: boolean}}, required: [status, cleared], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses retain + nullable first parent, complete parent arrays and explicit no_change result, identically in both + variants. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {req_id: {type: integer}, verification_id: {type: integer}}, required: [req_id, verification_id], additionalProperties: true}} + x-marreq-authorization: EditRequirements;project-membership;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/traceability/clear_suspect: + post: + operationId: traceability_clear_suspect_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, enum: [ok, no_change]}, cleared: {type: boolean}}, required: [status, cleared], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/traceability.rs and scoped guards. Cross-project entity pairs are rejected. Responses retain + nullable first parent, complete parent arrays and explicit no_change result, identically in both + variants. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {req_id: {type: integer}, verification_id: {type: integer}}, required: [req_id, verification_id], additionalProperties: true}} + x-marreq-bearer-scopes: + - traceability:write + x-marreq-authorization: EditRequirements;project-membership;not-archived + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/transfers.yaml b/docs/developer/openapi/paths/transfers.yaml new file mode 100644 index 00000000..745891b0 --- /dev/null +++ b/docs/developer/openapi/paths/transfers.yaml @@ -0,0 +1,602 @@ +/api/projects/{project_id}/exports/requirements.xlsx: + get: + operationId: exports_export_requirements_xlsx + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/verifications.xlsx: + get: + operationId: exports_export_verifications_xlsx + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/matrix.xlsx: + get: + operationId: exports_export_matrix_xlsx + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/dsm.xlsx: + get: + operationId: exports_export_dsm_xlsx + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: link_types + schema: {type: string} + required: false + - in: query + name: category_id + schema: {type: integer} + required: false + - in: query + name: root_id + schema: {type: integer} + required: false + - in: query + name: order + schema: {type: string, enum: [hierarchy, partition]} + required: false + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/matrix-links.xlsx: + get: + operationId: exports_export_matrix_links_xlsx + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/requirements.pdf: + get: + operationId: exports_export_requirements_pdf + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/pdf: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/report.pdf: + get: + operationId: exports_export_report_pdf + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/pdf: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/requirements.reqif: + get: + operationId: exports_export_requirements_reqif + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/xml: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/requirements.reqifz: + get: + operationId: exports_export_requirements_reqifz + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/zip: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/baselines/{filename}: + get: + operationId: exports_export_baseline_reqif + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: filename + required: true + schema: {type: string} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/xml: {schema: {type: string, format: binary}}, application/zip: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/bundle.json: + get: + operationId: exports_export_project_bundle + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/json: {schema: {type: object}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/exports/bundle.zip: + get: + operationId: exports_export_project_bundle_zip + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: File download; Content-Disposition supplies the filename. + headers: {Content-Disposition: {required: true, schema: {type: string}}} + content: {application/zip: {schema: {type: string, format: binary}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/exports.rs FileDownload/ArchiveDownload and export handlers. Both variants use the same + media and mandatory download filename header. Consumers treat file contents as opaque downloads; the + JSON bundle is a JSON object. Baseline filename selects .reqif or .reqifz and is validated server-side. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;ViewRequirements +/api/projects/{project_id}/imports/excel/preview: + post: + operationId: imports_preview_excel + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {import_type: {type: string}, columns: {type: array, items: {type: object, properties: {index: {type: integer}, name: {type: string}, sample_value: {type: string}}, required: [index, name, sample_value], additionalProperties: true}}, sample_rows: {type: array, items: {type: array, items: {type: string}}}, row_count: {type: integer}, available_fields: {type: object, properties: {requirements: {type: array, items: {type: string}}, tests: {type: array, items: {type: string}}, matrix: {type: array, items: {type: string}}}, required: [requirements, tests, matrix], additionalProperties: true}, unique_values: {type: object, additionalProperties: {type: array, items: {type: string}}}}, required: [import_type, columns, sample_rows, row_count, available_fields, unique_values], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '413': + $ref: ../components/responses.yaml#/ApiError + '415': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project permissions. + Size/content validation and Rocket multipart failures can return framework bodies. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: {schema: {type: object, properties: {file: {type: string, format: binary}}, required: [file], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/imports/excel: + post: + operationId: imports_commit_excel + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/common.yaml#/ExcelImportResult}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '413': + $ref: ../components/responses.yaml#/ApiError + '415': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project permissions. + Size/content validation and Rocket multipart failures can return framework bodies. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: {schema: {type: object, properties: {file: {type: string, format: binary}, import_type: {type: string}, column_mappings: {type: string}, value_mappings: {type: string}}, required: [file, import_type, column_mappings], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/imports/reqif: + post: + operationId: imports_commit_reqif + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {success: {type: boolean}, message: {type: string}, imported_count: {type: integer}, created_link_count: {type: integer}, imported_attachment_count: {type: integer}, documents: {type: array, items: {type: string}}, errors: {type: array, items: {type: string}}, warnings: {type: array, items: {type: string}}, imported_requirement_ids: {type: array, items: {type: integer}}}, required: [success, message, imported_count, created_link_count, imported_attachment_count, documents, errors, warnings, imported_requirement_ids], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '413': + $ref: ../components/responses.yaml#/ApiError + '415': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project permissions. + Size/content validation and Rocket multipart failures can return framework bodies. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: {schema: {type: object, properties: {file: {type: string, format: binary}}, required: [file], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/imports/bundle: + post: + operationId: imports_import_project_bundle + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {project_id: {type: integer}, slug: {type: string}, project_base_path: {type: string}, imported_counts: {type: object, properties: {categories: {type: integer}, applicability: {type: integer}, requirement_statuses: {type: integer}, verification_statuses: {type: integer}, verification_methods: {type: integer}, custom_fields: {type: integer}, requirements: {type: integer}, verifications: {type: integer}, matrix_links: {type: integer}, comments: {type: integer}, members: {type: integer}, reviewers: {type: integer}, attachments: {type: integer}, use_cases: {type: integer}, use_case_links: {type: integer}}, required: [categories, applicability, requirement_statuses, verification_statuses, verification_methods, custom_fields, requirements, verifications, matrix_links, comments, members, reviewers, attachments, use_cases, use_case_links], additionalProperties: true}, warnings: {type: array, items: {type: string}}, errors: {type: array, items: {type: string}}}, required: [project_id, slug, project_base_path, imported_counts, warnings, errors], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '413': + $ref: ../components/responses.yaml#/ApiError + '415': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/imports.rs, importers/excel.rs and project_bundle.rs, plus ReqIF ImportResult. + Session/personal token; delegated OAuth denied. Both variants validate uploads and project permissions. + Size/content validation and Rocket multipart failures can return framework bodies. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: {schema: {type: object, properties: {file: {type: string, format: binary}, group_id: {type: integer}}, required: [file], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;group-ManageProjects + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/use-case-links.yaml b/docs/developer/openapi/paths/use-case-links.yaml new file mode 100644 index 00000000..94ed8259 --- /dev/null +++ b/docs/developer/openapi/paths/use-case-links.yaml @@ -0,0 +1,350 @@ +/api/projects/{project_id}/use-cases/{id}/links: + get: + operationId: use_cases_list_links + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_LinkView_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + post: + operationId: use_cases_create_link + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_LinkView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:write + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {element_id: {anyOf: [{type: string}, {type: 'null'}]}, requirement_id: {anyOf: [{type: integer}, {type: 'null'}]}, verification_id: {anyOf: [{type: integer}, {type: 'null'}]}, rationale: {anyOf: [{type: string}, {type: 'null'}]}}, additionalProperties: true}} + x-marreq-csrf: session-csrf +/api/projects/{project_id}/use-cases/{id}/links/{link_id}: + delete: + operationId: use_cases_delete_link + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': + description: Deleted + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + x-marreq-bearer-scopes: + - use_cases:write + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: + post: + operationId: use_cases_clear_link_suspect + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: link_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_ClearSuspectResponse_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + x-marreq-bearer-scopes: + - use_cases:write + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/requirements/{requirement_id}/use-cases: + get: + operationId: use_cases_requirement_use_cases + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: requirement_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseRef_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/verifications/{verification_id}/use-cases: + get: + operationId: use_cases_verification_use_cases + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseRef_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/comments: + get: + operationId: use_cases_list_comments + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: version_id + required: false + schema: {anyOf: [{type: integer}, {type: 'null'}]} + - in: query + name: element_id + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseCommentView_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + post: + operationId: use_cases_create_comment + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseCommentView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:write + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {body: {type: string}, use_case_version_id: {anyOf: [{type: integer}, {type: 'null'}]}, element_id: {anyOf: [{type: string}, {type: 'null'}]}}, additionalProperties: true, required: [body]}} + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/paths/use-cases.yaml b/docs/developer/openapi/paths/use-cases.yaml new file mode 100644 index 00000000..68d5d7f5 --- /dev/null +++ b/docs/developer/openapi/paths/use-cases.yaml @@ -0,0 +1,675 @@ +/api/projects/{project_id}/use-cases: + get: + operationId: use_cases_list + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: query + name: q + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + - in: query + name: approval_state + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + - in: query + name: actor + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + - in: query + name: has_suspect + required: false + schema: {anyOf: [{type: boolean}, {type: 'null'}]} + - in: query + name: requirement_id + required: false + schema: {anyOf: [{type: integer}, {type: 'null'}]} + - in: query + name: verification_id + required: false + schema: {anyOf: [{type: integer}, {type: 'null'}]} + - in: query + name: sort_column + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + - in: query + name: sort_dir + required: false + schema: {anyOf: [{type: string}, {type: 'null'}]} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseRow_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + post: + operationId: use_cases_create + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseDetail_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Create the shared draft; optional reference_code, title, primary_actor and document. A missing code is + generated. Supports Idempotency-Key. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:write + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {reference_code: {type: [string, 'null']}, title: {type: [string, 'null']}, primary_actor: {type: [string, 'null']}, document: {anyOf: [{$ref: ../schemas/use-cases.yaml#/UseCaseDocument}, {type: 'null'}]}}, additionalProperties: true}} +/api/projects/{project_id}/use-cases/next-code: + get: + operationId: use_cases_next_code + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_NextCode_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}: + get: + operationId: use_cases_get + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: version_id + required: false + schema: {anyOf: [{type: integer}, {type: 'null'}]} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_UseCaseDetail_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + delete: + operationId: use_cases_delete + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': + description: Deleted + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + x-marreq-bearer-scopes: + - use_cases:write + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/draft: + put: + operationId: use_cases_put_draft + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_DraftChangeView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_PutDraftRequest_request}} + x-marreq-bearer-scopes: + - use_cases:write + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + delete: + operationId: use_cases_discard_draft + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '204': + description: Deleted + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + x-marreq-bearer-scopes: + - use_cases:write + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. + get: + operationId: use_cases_get_draft + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_DraftView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/draft/operations: + post: + operationId: use_cases_draft_operations + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_DraftChangeView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:write + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {base_revision: {anyOf: [{type: integer}, {type: 'null'}]}, operations: {type: array, items: {$ref: ../schemas/use-cases.yaml#/UseCaseOperation}}}, additionalProperties: true, required: [operations]}} + x-marreq-csrf: session-csrf +/api/projects/{project_id}/use-cases/{id}/draft/publish: + post: + operationId: use_cases_publish + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Safe replay for the same principal, target, operation and payload; changed payload returns 409. + responses: + '201': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-cases.yaml#/UseCasePublishResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:write + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {base_revision: {type: integer}, change_note: {anyOf: [{type: string}, {type: 'null'}]}}, additionalProperties: true, required: [base_revision]}} + x-marreq-csrf: session-csrf +/api/projects/{project_id}/use-cases/{id}/versions: + get: + operationId: use_cases_list_versions + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_VersionSummary_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: + put: + operationId: use_cases_set_version_approval + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: version_id + required: true + schema: {type: integer} + - in: header + name: X-CSRF-Token + required: false + schema: {type: string} + description: Required for session mutations; Bearer requests use scoped authentication instead. + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_VersionView_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-csrf: session-csrf + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/use-case-views.yaml#/use_cases_SetApprovalRequest_request}} + x-marreq-bearer-scopes: + - use_cases:approve + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. +/api/projects/{project_id}/use-cases/{id}/draft/diff: + get: + operationId: use_cases_draft_diff + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: query + name: include_unchanged + required: false + schema: {type: boolean} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-cases.yaml#/UseCaseDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:read +/api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: + get: + operationId: use_cases_version_diff + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: a + required: true + schema: {type: integer} + - in: path + name: b + required: true + schema: {type: integer} + - in: query + name: include_unchanged + required: false + schema: {type: boolean} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/use-cases.yaml#/UseCaseDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Guard/fairing/validation failure; body may be empty or framework generated + '403': + description: Guard/fairing/validation failure; body may be empty or framework generated + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Guard/fairing/validation failure; body may be empty or framework generated + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Use-case service contract; see use-cases-api.md for revision conflicts, document validation, + idempotency, publishing, links and review permissions. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - use_cases:read +/api/projects/{project_id}/baselines/{baseline_id}/use-cases: + get: + operationId: use_cases_baseline_use_cases + security: + - cookieAuth: [] + - bearerAuth: [] + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: baseline_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/use-case-views.yaml#/use_cases_BaselineUseCaseView_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '403': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket validation failure. Body may be empty or framework generated; do not require an ApiError body. + '500': + $ref: ../components/responses.yaml#/ApiError + x-marreq-bearer-scopes: + - use_cases:read + description: >- + Session or scoped Bearer authentication; project membership and ViewRequirements/EditRequirements/review + permissions apply. Archived projects reject mutations. See use-cases-api.md for validation and + revision-conflict semantics. diff --git a/docs/developer/openapi/paths/users.yaml b/docs/developer/openapi/paths/users.yaml new file mode 100644 index 00000000..0a47a020 --- /dev/null +++ b/docs/developer/openapi/paths/users.yaml @@ -0,0 +1,511 @@ +/api/groups: + get: + operationId: groups_list + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/users.yaml#/GroupResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup + post: + operationId: groups_create + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/users.yaml#/GroupResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {name: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}, owner_id: {anyOf: [{type: integer}, {type: 'null'}]}}, required: [name], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;authenticated + x-marreq-csrf: session-csrf +/api/groups/creatable: + get: + operationId: groups_list_creatable + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/users.yaml#/GroupResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageProjects +/api/groups/{group_id}: + get: + operationId: groups_get + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/users.yaml#/GroupResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup + patch: + operationId: groups_update + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/users.yaml#/GroupResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {name: {type: string}, description: {anyOf: [{type: string}, {type: 'null'}]}}, required: [name], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf + delete: + operationId: groups_delete + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf +/api/groups/{group_id}/projects: + get: + operationId: groups_list_projects + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/projects.yaml#/Project}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup +/api/groups/{group_id}/members: + get: + operationId: groups_list_members + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/users.yaml#/GroupMemberResponse}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ViewGroup +/api/groups/{group_id}/members/{user_id}: + put: + operationId: groups_set_member_role + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/users.yaml#/GroupMemberResponse}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {role: {type: integer}}, required: [role], additionalProperties: true}} + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf + delete: + operationId: groups_remove_member + parameters: + - in: path + name: group_id + required: true + schema: {type: integer} + - in: path + name: user_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/groups.rs, GroupService and models/forms.rs. Same Server/Cloud serialization; session or + personal API token, delegated OAuth denied. Group membership and the operation-specific group permission + are enforced. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: personal-token-or-session;delegated-denied;ManageGroupMembers + x-marreq-csrf: session-csrf +/api/users/{id}: + put: + operationId: users_update + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/entities_User_response}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud returns + 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/users.yaml#/UpdateUser}} + x-marreq-authorization: instance-admin;deployment-admin-promotion-policy + x-marreq-csrf: session-csrf + delete: + operationId: users_delete + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud returns + 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin;deployment-admin-deletion-policy + x-marreq-csrf: session-csrf +/api/users/{id}/password: + put: + operationId: users_set_password + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud returns + 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {new_password: {type: string}, confirm_password: {type: string}}, required: [new_password, confirm_password], additionalProperties: true}} + x-marreq-authorization: instance-admin + x-marreq-csrf: session-csrf +/api/users: + post: + operationId: users_create + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '409': + $ref: ../components/responses.yaml#/ApiError + '410': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud returns + 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/users.yaml#/UserCreateRequest}} + x-marreq-authorization: instance-admin;allows_self_administered_user_creation + x-marreq-csrf: session-csrf + get: + operationId: users_list + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/auth.yaml#/entities_User_response}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/users.rs and services/user_service.rs. Session instance administrator only. Cloud returns + 410 for user creation and restricts promotion/deletion of the designated site administrator. + security: + - cookieAuth: [] + x-marreq-authorization: instance-admin diff --git a/docs/developer/openapi/paths/verifications.yaml b/docs/developer/openapi/paths/verifications.yaml new file mode 100644 index 00000000..bbb98766 --- /dev/null +++ b/docs/developer/openapi/paths/verifications.yaml @@ -0,0 +1,791 @@ +/api/projects/{project_id}/verifications/{id}/activity: + get: + operationId: activity_verification_activity_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/common.yaml#/EntityActivityItem}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/activity.rs EntityActivityItem and scoped project guards. Same serialization, permission and + project-boundary rules in Server and Cloud. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/verifications/{verification_id}/matrix: + get: + operationId: matrix_get_verification_matrix + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {verification_id: {type: integer}, requirement_ids: {type: array, items: {type: integer}}}, required: [verification_id, requirement_ids], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every entity + belongs to the project; both variants share the shape. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - traceability:read + x-marreq-authorization: project-membership;ViewRequirements + put: + operationId: matrix_put_verification_matrix + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string}, verification_id: {type: integer}, requirement_ids: {type: array, items: {type: integer}}}, required: [status, verification_id, requirement_ids], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/matrix.rs, MatrixLink and scoped traceability guards. Matrix replacement checks every entity + belongs to the project; both variants share the shape. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {requirement_ids: {type: array, items: {type: integer}}}, required: [requirement_ids], additionalProperties: true}} + x-marreq-bearer-scopes: + - traceability:write + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verifications/{verification_id}/control: + get: + operationId: verification_control_get_verification_control + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/VerificationControlView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs are + cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements + put: + operationId: verification_control_put_verification_control + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: verification_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/traceability.yaml#/VerificationControlView}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_control.rs and verification_control_service.rs. Optional nullable inputs are + cleared when absent; unknown fields rejected. Both variants enforce scoped guards, permissions, + trimming/length validation and reviewer rules. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/traceability.yaml#/VerificationControlInput}} + x-marreq-bearer-scopes: + - verifications:write + x-marreq-authorization: project-membership;EditRequirements;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verifications/{id}/snapshots: + get: + operationId: verification_diff_list_snapshots_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {type: object, properties: {id: {type: integer}, created_at: {type: string}, name: {type: string}, reference_code: {type: string}, status_id: {type: integer}}, required: [id, created_at, name, reference_code, status_id], additionalProperties: true}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both versions/snapshots + must belong to the requested entity and project. Optional SingleValueDiff/label fields are omitted, not + null. Shared wire representation in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: + get: + operationId: verification_diff_diff_snapshots_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + - in: path + name: v1 + required: true + schema: {type: integer} + - in: path + name: v2 + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/baselines.yaml#/VerificationVersionDiff}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api requirement/verification diff routes, diff/mod.rs and diff services. Both versions/snapshots + must belong to the requested entity and project. Optional SingleValueDiff/label fields are omitted, not + null. Shared wire representation in both variants. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements +/api/projects/{project_id}/verification-methods: + get: + operationId: verification_methods_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/verifications.yaml#/VerificationMethod}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: ViewRequirements + post: + operationId: verification_methods_create_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/verifications.yaml#/NewVerificationMethod}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verification-methods/{method_id}: + put: + operationId: verification_methods_update_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: method_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/auth.yaml#/AuthStatus}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/verifications.yaml#/NewVerificationMethod}} + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf + delete: + operationId: verification_methods_delete_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: method_id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/verification_methods.rs, models/entities.rs and models/forms.rs. Shared Server/Cloud wire + shape; reads filter by ViewRequirements, mutations require ManageProjectConfiguration. System status + mutations are rejected. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-authorization: ManageProjectConfiguration;not-archived + x-marreq-csrf: session-csrf +/api/verifications: + get: + operationId: verifications_list + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/verifications.yaml#/Verification}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + x-marreq-authorization: session;ViewRequirements + post: + operationId: verifications_create + parameters: [] + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/verifications.yaml#/NewVerification}} + x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verifications: + get: + operationId: verifications_list_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: array, items: {$ref: ../schemas/verifications.yaml#/Verification}}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements + post: + operationId: verifications_create_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: header + name: Idempotency-Key + required: false + schema: {type: string} + description: Optional replay identity, trimmed then validated as 1โ€“200 ASCII letters, digits, dot, underscore or hyphen. Reuse with a different payload returns 409. + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {status: {type: string, const: ok}, id: {type: integer}}, required: [status, id], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/verifications.yaml#/NewVerification}} + x-marreq-bearer-scopes: + - verifications:write + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verifications/{id}: + get: + operationId: verifications_get_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/verifications.yaml#/Verification}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + x-marreq-bearer-scopes: + - verifications:read + x-marreq-authorization: project-membership;ViewRequirements + put: + operationId: verifications_update_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/verifications.yaml#/Verification}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {$ref: ../schemas/verifications.yaml#/NewVerification}} + x-marreq-bearer-scopes: + - verifications:write + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/verifications/{id}: + get: + operationId: verifications_get + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {$ref: ../schemas/verifications.yaml#/Verification}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + x-marreq-authorization: session;ViewRequirements + delete: + operationId: verifications_delete + parameters: + - in: path + name: id + required: true + schema: {type: integer} + responses: + '204': + description: Success; empty body. + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + x-marreq-authorization: session;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf +/api/projects/{project_id}/verifications/{id}/field: + post: + operationId: verifications_update_field_by_project + parameters: + - in: path + name: project_id + required: true + schema: {type: integer} + - in: path + name: id + required: true + schema: {type: integer} + responses: + '200': + description: Success + content: {application/json: {schema: {type: object, properties: {success: {type: boolean}, message: {type: string}}, required: [success, message], additionalProperties: true}}} + '400': + $ref: ../components/responses.yaml#/ApiError + '401': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '403': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '404': + $ref: ../components/responses.yaml#/ApiError + '409': + $ref: ../components/responses.yaml#/ApiError + '422': + description: Authentication, authorization, CSRF or Rocket input failure; body may be empty or framework-generated. + '500': + $ref: ../components/responses.yaml#/ApiError + description: >- + Audited api/requirements.rs, api/verifications.rs and models/entities.rs/forms.rs. Both variants use the + same serializers. Scoped routes check project boundaries and scopes; mutations require EditRequirements + and unarchived projects, with project-reviewer checks for status/approval transitions. Idempotent + creates reject conflicting payloads with 409. + security: + - cookieAuth: [] + - bearerAuth: [] + requestBody: + required: true + content: + application/json: {schema: {type: object, properties: {field: {type: string}, value: {type: string}}, required: [field, value], additionalProperties: true}} + x-marreq-bearer-scopes: + - verifications:write + x-marreq-authorization: project-membership;EditRequirements;conditional-project-reviewer;not-archived + x-marreq-csrf: session-csrf diff --git a/docs/developer/openapi/root.yaml b/docs/developer/openapi/root.yaml new file mode 100644 index 00000000..9911f230 --- /dev/null +++ b/docs/developer/openapi/root.yaml @@ -0,0 +1,612 @@ +openapi: 3.1.0 +info: + title: Marreq HTTP API + version: 0.1.0 + description: >- + Consumer-facing HTTP contracts. Unconsumed backend routes are outside compatibility enforcement. Required + operations marked x-marreq-unverified fail verification; no missing schemas are inferred from inventory + entries. +servers: + - url: / +paths: + /api/cache/stats: + $ref: ./paths/admin.yaml#/~1api~1cache~1stats + /api/cache/recommendations: + $ref: ./paths/admin.yaml#/~1api~1cache~1recommendations + /api/cache/clear: + $ref: ./paths/admin.yaml#/~1api~1cache~1clear + /api/cache/cleanup: + $ref: ./paths/admin.yaml#/~1api~1cache~1cleanup + /api/cache/reset-counters: + $ref: ./paths/admin.yaml#/~1api~1cache~1reset-counters + /api/projects/{project_id}/requirements/{id}/activity: + $ref: ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{id}~1activity + /api/projects/{project_id}/verifications/{id}/activity: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{id}~1activity + /api/applicability: + $ref: ./paths/catalog.yaml#/~1api~1applicability + /api/applicability/{id}: + $ref: ./paths/catalog.yaml#/~1api~1applicability~1{id} + /api/projects/{project_id}/attachments: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1attachments + /api/projects/{project_id}/attachments/{attachment_id}/download: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1attachments~1{attachment_id}~1download + /api/projects/{project_id}/storage: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1storage + /api/projects/{project_id}/storage/quota: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1storage~1quota + /api/projects/{project_id}/baselines/{baseline_id}/attachments: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1attachments + /api/projects/{project_id}/baselines/{baseline_id}/attachments/{attachment_id}/download: + $ref: >- + ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1attachments~1{attachment_id}~1download + /api/auth/providers: + $ref: ./paths/auth.yaml#/~1api~1auth~1providers + /api/auth/external/{provider}/start: + $ref: ./paths/auth.yaml#/~1api~1auth~1external~1{provider}~1start + /api/auth/external/{provider}/callback: + $ref: ./paths/auth.yaml#/~1api~1auth~1external~1{provider}~1callback + /api/auth/identities: + $ref: ./paths/auth.yaml#/~1api~1auth~1identities + /api/auth/me: + $ref: ./paths/auth.yaml#/~1api~1auth~1me + /api/admin/backup/info: + $ref: ./paths/admin.yaml#/~1api~1admin~1backup~1info + /api/admin/backup: + $ref: ./paths/admin.yaml#/~1api~1admin~1backup + /api/projects/{project_id}/baselines: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines + /api/projects/{project_id}/baselines/{baseline_id}: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id} + /api/projects/{project_id}/baselines/{baseline_id}/requirements: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1requirements + /api/projects/{project_id}/baselines/{baseline_id}/traceability: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1traceability + /api/projects/{project_id}/baselines/{baseline_id}/verifications: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1verifications + /api/projects/{project_id}/baselines/diff: + $ref: ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1diff + /api/projects/{project_id}/catalog: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1catalog + /api/categories: + $ref: ./paths/catalog.yaml#/~1api~1categories + /api/categories/{id}: + $ref: ./paths/catalog.yaml#/~1api~1categories~1{id} + /api/projects/{project_id}/requirements/{requirement_id}/comments: + $ref: >- + ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{requirement_id}~1comments + /api/requirements/{requirement_id}/comments: + $ref: ./paths/requirement-history.yaml#/~1api~1requirements~1{requirement_id}~1comments + /api/projects/{project_id}/custom_fields: + $ref: ./paths/project-settings.yaml#/~1api~1projects~1{project_id}~1custom_fields + /api/projects/{project_id}/custom_fields/{field_id}: + $ref: ./paths/project-settings.yaml#/~1api~1projects~1{project_id}~1custom_fields~1{field_id} + /api/projects/{project_id}/dsm: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1dsm + /api/projects/{project_id}/exports/requirements.xlsx: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1requirements.xlsx + /api/projects/{project_id}/exports/verifications.xlsx: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1verifications.xlsx + /api/projects/{project_id}/exports/matrix.xlsx: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1matrix.xlsx + /api/projects/{project_id}/exports/dsm.xlsx: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1dsm.xlsx + /api/projects/{project_id}/exports/matrix-links.xlsx: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1matrix-links.xlsx + /api/projects/{project_id}/exports/requirements.pdf: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1requirements.pdf + /api/projects/{project_id}/exports/report.pdf: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1report.pdf + /api/projects/{project_id}/exports/requirements.reqif: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1requirements.reqif + /api/projects/{project_id}/exports/requirements.reqifz: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1requirements.reqifz + /api/projects/{project_id}/exports/baselines/{filename}: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1baselines~1{filename} + /api/projects/{project_id}/exports/bundle.json: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1bundle.json + /api/projects/{project_id}/exports/bundle.zip: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1exports~1bundle.zip + /api/groups: + $ref: ./paths/users.yaml#/~1api~1groups + /api/groups/creatable: + $ref: ./paths/users.yaml#/~1api~1groups~1creatable + /api/groups/{group_id}: + $ref: ./paths/users.yaml#/~1api~1groups~1{group_id} + /api/groups/{group_id}/projects: + $ref: ./paths/users.yaml#/~1api~1groups~1{group_id}~1projects + /api/groups/{group_id}/members: + $ref: ./paths/users.yaml#/~1api~1groups~1{group_id}~1members + /api/groups/{group_id}/members/{user_id}: + $ref: ./paths/users.yaml#/~1api~1groups~1{group_id}~1members~1{user_id} + /api/home/attention: + $ref: ./paths/projects.yaml#/~1api~1home~1attention + /api/search: + $ref: ./paths/projects.yaml#/~1api~1search + /api/projects/{project_id}/imports/excel/preview: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1imports~1excel~1preview + /api/projects/{project_id}/imports/excel: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1imports~1excel + /api/projects/{project_id}/imports/reqif: + $ref: ./paths/transfers.yaml#/~1api~1projects~1{project_id}~1imports~1reqif + /api/projects/imports/bundle: + $ref: ./paths/transfers.yaml#/~1api~1projects~1imports~1bundle + /api/admin/logs: + $ref: ./paths/admin.yaml#/~1api~1admin~1logs + /api/admin/logs/stats: + $ref: ./paths/admin.yaml#/~1api~1admin~1logs~1stats + /api/admin/logs/export.json: + $ref: ./paths/admin.yaml#/~1api~1admin~1logs~1export.json + /api/admin/logs/cleanup: + $ref: ./paths/admin.yaml#/~1api~1admin~1logs~1cleanup + /api/projects/{project_id}/matrix: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1matrix + /api/projects/{project_id}/verifications/{verification_id}/matrix: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{verification_id}~1matrix + /api/mcp/principal: + $ref: ./paths/mcp.yaml#/~1api~1mcp~1principal + /api/mcp/audit: + $ref: ./paths/mcp.yaml#/~1api~1mcp~1audit + /api/mcp/internal/audit: + $ref: ./paths/mcp.yaml#/~1api~1mcp~1internal~1audit + /api/projects/{project_id}/me/permissions: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1me~1permissions + /api/projects/{project_id}/members: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1members + /api/projects/{project_id}/members/{user_id}: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1members~1{user_id} + /api/projects/{project_id}/reviewers: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1reviewers + /api/meta/deployment: + $ref: ./paths/projects.yaml#/~1api~1meta~1deployment + /api/meta/build: + $ref: ./paths/projects.yaml#/~1api~1meta~1build + /api/notifications: + $ref: ./paths/notifications.yaml#/~1api~1notifications + /api/notifications/{id}/read: + $ref: ./paths/notifications.yaml#/~1api~1notifications~1{id}~1read + /api/notifications/read-all: + $ref: ./paths/notifications.yaml#/~1api~1notifications~1read-all + /api/notifications/preferences: + $ref: ./paths/notifications.yaml#/~1api~1notifications~1preferences + /api/notifications/preferences/{project_id}: + $ref: ./paths/notifications.yaml#/~1api~1notifications~1preferences~1{project_id} + /.well-known/oauth-authorization-server: + $ref: ./paths/auth.yaml#/~1.well-known~1oauth-authorization-server + /.well-known/oauth-protected-resource/mcp: + $ref: ./paths/auth.yaml#/~1.well-known~1oauth-protected-resource~1mcp + /oauth/register: + $ref: ./paths/auth.yaml#/~1oauth~1register + /oauth/authorize: + $ref: ./paths/auth.yaml#/~1oauth~1authorize + /oauth/token: + $ref: ./paths/auth.yaml#/~1oauth~1token + /api/projects: + $ref: ./paths/projects.yaml#/~1api~1projects + /api/projects/{project_id}: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id} + /api/projects/{project_id}/archive: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1archive + /api/projects/{project_id}/unarchive: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1unarchive + /api/project-from-path/{slug}: + $ref: ./paths/projects.yaml#/~1api~1project-from-path~1{slug} + /api/projects/{project_id}/reports/types: + $ref: ./paths/reports.yaml#/~1api~1projects~1{project_id}~1reports~1types + /api/projects/{project_id}/report_templates: + $ref: ./paths/reports.yaml#/~1api~1projects~1{project_id}~1report_templates + /api/projects/{project_id}/report_templates/{template_id}: + $ref: ./paths/reports.yaml#/~1api~1projects~1{project_id}~1report_templates~1{template_id} + /api/projects/{project_id}/reports/{file}: + $ref: ./paths/reports.yaml#/~1api~1projects~1{project_id}~1reports~1{file} + /api/projects/{project_id}/requirements/{req_id}/versions/{v1}/diff/{v2}: + $ref: >- + ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{req_id}~1versions~1{v1}~1diff~1{v2} + /api/projects/{project_id}/baselines/{baseline_id}/requirements/{req_id}/diff/current: + $ref: >- + ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1requirements~1{req_id}~1diff~1current + /api/projects/{project_id}/requirement-version-links: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1requirement-version-links + /api/projects/{project_id}/requirement-version-links/{link_id}: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1requirement-version-links~1{link_id} + /api/projects/{project_id}/requirement-version-links/link-types: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1requirement-version-links~1link-types + /api/projects/{project_id}/requirements: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements + /api/requirements/{id}: + $ref: ./paths/requirements.yaml#/~1api~1requirements~1{id} + /api/projects/{project_id}/requirements/{id}: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{id} + /api/projects/{project_id}/requirements/{id}/versions: + $ref: ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{id}~1versions + /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}: + $ref: >- + ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{req_id}~1versions~1{version_id} + /api/projects/{project_id}/requirements/{id}/impacted_tests: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{id}~1impacted_tests + /api/projects/{project_id}/requirements/{req_id}/versions/{version_id}/approval: + $ref: >- + ./paths/requirement-history.yaml#/~1api~1projects~1{project_id}~1requirements~1{req_id}~1versions~1{version_id}~1approval + /api/projects/{project_id}/saved_views: + $ref: ./paths/project-settings.yaml#/~1api~1projects~1{project_id}~1saved_views + /api/projects/{project_id}/saved_views/{view_id}: + $ref: ./paths/project-settings.yaml#/~1api~1projects~1{project_id}~1saved_views~1{view_id} + /api/projects/{project_id}/requirements/semantic_search: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1semantic_search + /api/projects/{project_id}/requirements/ask: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1ask + /api/projects/{project_id}/requirements/index_status: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1index_status + /api/status: + $ref: ./paths/catalog.yaml#/~1api~1status + /api/status/{id}: + $ref: ./paths/catalog.yaml#/~1api~1status~1{id} + /api/projects/{project_id}/requirements/{id}/trace_up: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{id}~1trace_up + /api/projects/{project_id}/requirements/{id}/trace_down: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{id}~1trace_down + /api/projects/{project_id}/coverage_report: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1coverage_report + /api/traceability/clear_suspect: + $ref: ./paths/traceability.yaml#/~1api~1traceability~1clear_suspect + /api/projects/{project_id}/traceability/clear_suspect: + $ref: ./paths/traceability.yaml#/~1api~1projects~1{project_id}~1traceability~1clear_suspect + /api/projects/{project_id}/use-cases: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases + /api/projects/{project_id}/use-cases/next-code: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1next-code + /api/projects/{project_id}/use-cases/{id}: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id} + /api/projects/{project_id}/use-cases/{id}/draft: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1draft + /api/projects/{project_id}/use-cases/{id}/draft/operations: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1draft~1operations + /api/projects/{project_id}/use-cases/{id}/draft/publish: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1draft~1publish + /api/projects/{project_id}/use-cases/{id}/versions: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1versions + /api/projects/{project_id}/use-cases/{id}/versions/{version_id}/approval: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1versions~1{version_id}~1approval + /api/projects/{project_id}/use-cases/{id}/links: + $ref: ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1links + /api/projects/{project_id}/use-cases/{id}/links/{link_id}: + $ref: ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1links~1{link_id} + /api/projects/{project_id}/use-cases/{id}/links/{link_id}/clear_suspect: + $ref: >- + ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1links~1{link_id}~1clear_suspect + /api/projects/{project_id}/requirements/{requirement_id}/use-cases: + $ref: ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1requirements~1{requirement_id}~1use-cases + /api/projects/{project_id}/verifications/{verification_id}/use-cases: + $ref: ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1verifications~1{verification_id}~1use-cases + /api/projects/{project_id}/use-cases/{id}/draft/diff: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1draft~1diff + /api/projects/{project_id}/use-cases/{id}/versions/{a}/diff/{b}: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1versions~1{a}~1diff~1{b} + /api/projects/{project_id}/use-cases/{id}/comments: + $ref: ./paths/use-case-links.yaml#/~1api~1projects~1{project_id}~1use-cases~1{id}~1comments + /api/projects/{project_id}/baselines/{baseline_id}/use-cases: + $ref: ./paths/use-cases.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1use-cases + /api/users/{id}: + $ref: ./paths/users.yaml#/~1api~1users~1{id} + /api/users/{id}/password: + $ref: ./paths/users.yaml#/~1api~1users~1{id}~1password + /api/projects/{project_id}/verifications/{verification_id}/control: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{verification_id}~1control + /api/projects/{project_id}/requirements/{requirement_id}/close_out: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{requirement_id}~1close_out + /api/projects/{project_id}/close_out: + $ref: ./paths/projects.yaml#/~1api~1projects~1{project_id}~1close_out + /api/projects/{project_id}/requirements/{requirement_id}/compliance: + $ref: ./paths/requirements.yaml#/~1api~1projects~1{project_id}~1requirements~1{requirement_id}~1compliance + /api/projects/{project_id}/verifications/{id}/snapshots: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{id}~1snapshots + /api/projects/{project_id}/verifications/{id}/snapshots/{v1}/diff/{v2}: + $ref: >- + ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{id}~1snapshots~1{v1}~1diff~1{v2} + /api/projects/{project_id}/baselines/{baseline_id}/verifications/{id}/diff/current: + $ref: >- + ./paths/baselines.yaml#/~1api~1projects~1{project_id}~1baselines~1{baseline_id}~1verifications~1{id}~1diff~1current + /api/projects/{project_id}/verification-methods: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verification-methods + /api/projects/{project_id}/verification-methods/{method_id}: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verification-methods~1{method_id} + /api/verification-status: + $ref: ./paths/catalog.yaml#/~1api~1verification-status + /api/verification-status/{id}: + $ref: ./paths/catalog.yaml#/~1api~1verification-status~1{id} + /api/verifications: + $ref: ./paths/verifications.yaml#/~1api~1verifications + /api/projects/{project_id}/verifications: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications + /api/projects/{project_id}/verifications/{id}: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{id} + /api/verifications/{id}: + $ref: ./paths/verifications.yaml#/~1api~1verifications~1{id} + /api/projects/{project_id}/verifications/{id}/field: + $ref: ./paths/verifications.yaml#/~1api~1projects~1{project_id}~1verifications~1{id}~1field + /api/auth/csrf: + $ref: ./paths/auth.yaml#/~1api~1auth~1csrf + /api/auth/identities/{identity_id}: + $ref: ./paths/auth.yaml#/~1api~1auth~1identities~1{identity_id} + /api/auth/register: + $ref: ./paths/auth.yaml#/~1api~1auth~1register + /api/auth/login: + $ref: ./paths/auth.yaml#/~1api~1auth~1login + /api/users: + $ref: ./paths/users.yaml#/~1api~1users + /api/projects/{project_id}/attachments/{attachment_id}: + $ref: ./paths/attachments.yaml#/~1api~1projects~1{project_id}~1attachments~1{attachment_id} + /api/auth/external/{provider}/link: + $ref: ./paths/auth.yaml#/~1api~1auth~1external~1{provider}~1link + /api/auth/verify-email: + $ref: ./paths/auth.yaml#/~1api~1auth~1verify-email + /api/auth/logout: + $ref: ./paths/auth.yaml#/~1api~1auth~1logout + /api/auth/forgot-password: + $ref: ./paths/auth.yaml#/~1api~1auth~1forgot-password + /api/notifications/unread-count: + $ref: ./paths/notifications.yaml#/~1api~1notifications~1unread-count + /api/auth/change-password: + $ref: ./paths/auth.yaml#/~1api~1auth~1change-password + /api/oauth/grants: + $ref: ./paths/auth.yaml#/~1api~1oauth~1grants + /api/oauth/grants/{id}: + $ref: ./paths/auth.yaml#/~1api~1oauth~1grants~1{id} + /api/dashboard: + $ref: ./paths/projects.yaml#/~1api~1dashboard + /api/auth/reset-password: + $ref: ./paths/auth.yaml#/~1api~1auth~1reset-password +components: + securitySchemes: + cookieAuth: + $ref: ./components/securitySchemes.yaml#/cookieAuth + bearerAuth: + $ref: ./components/securitySchemes.yaml#/bearerAuth + mcpServiceAuth: + $ref: ./components/securitySchemes.yaml#/mcpServiceAuth + schemas: + SemanticSearchResult: + $ref: ./schemas/requirements.yaml#/SemanticSearchResult + RagAnswerResponse: + $ref: ./schemas/requirements.yaml#/RagAnswerResponse + OAuthTokenRequest: + $ref: ./schemas/auth.yaml#/OAuthTokenRequest + ReportDefinition: + $ref: ./schemas/reports.yaml#/ReportDefinition + ReportDefinitionInput: + $ref: ./schemas/reports.yaml#/ReportDefinitionInput + ReportOptionSpec: + $ref: ./schemas/reports.yaml#/ReportOptionSpec + ReportSectionSpec: + $ref: ./schemas/reports.yaml#/ReportSectionSpec + ReportTemplateView: + $ref: ./schemas/reports.yaml#/ReportTemplateView + ReportTemplateInput: + $ref: ./schemas/reports.yaml#/ReportTemplateInput + ExcelImportResult: + $ref: ./schemas/common.yaml#/ExcelImportResult + CustomFieldValueDisplay: + $ref: ./schemas/requirements.yaml#/CustomFieldValueDisplay + Requirement: + $ref: ./schemas/requirements.yaml#/Requirement + RequirementVersion: + $ref: ./schemas/requirements.yaml#/RequirementVersion + RequirementListRow: + $ref: ./schemas/requirements.yaml#/RequirementListRow + RequirementWithTraceSummary: + $ref: ./schemas/requirements.yaml#/RequirementWithTraceSummary + RequirementVersionDetail: + $ref: ./schemas/requirements.yaml#/RequirementVersionDetail + CustomFieldValueInput: + $ref: ./schemas/requirements.yaml#/CustomFieldValueInput + RequirementCreateRequest: + $ref: ./schemas/requirements.yaml#/RequirementCreateRequest + RequirementPatch: + $ref: ./schemas/requirements.yaml#/RequirementPatch + Verification: + $ref: ./schemas/verifications.yaml#/Verification + NewVerification: + $ref: ./schemas/verifications.yaml#/NewVerification + Baseline: + $ref: ./schemas/baselines.yaml#/Baseline + BaselineTraceability: + $ref: ./schemas/baselines.yaml#/BaselineTraceability + BaselineVerification: + $ref: ./schemas/baselines.yaml#/BaselineVerification + BaselineDiff: + $ref: ./schemas/baselines.yaml#/BaselineDiff + TextDiffResult: + $ref: ./schemas/baselines.yaml#/TextDiffResult + SingleValueDiff: + $ref: ./schemas/baselines.yaml#/SingleValueDiff + VerificationDiff: + $ref: ./schemas/baselines.yaml#/VerificationDiff + RequirementDiff: + $ref: ./schemas/baselines.yaml#/RequirementDiff + VerificationVersionDiff: + $ref: ./schemas/baselines.yaml#/VerificationVersionDiff + Dsm: + $ref: ./schemas/traceability.yaml#/Dsm + EntityActivityItem: + $ref: ./schemas/common.yaml#/EntityActivityItem + MatrixLink: + $ref: ./schemas/traceability.yaml#/MatrixLink + VerificationControlView: + $ref: ./schemas/traceability.yaml#/VerificationControlView + RequirementCloseOutView: + $ref: ./schemas/traceability.yaml#/RequirementCloseOutView + VerificationControlInput: + $ref: ./schemas/traceability.yaml#/VerificationControlInput + RequirementVersionLink: + $ref: ./schemas/traceability.yaml#/RequirementVersionLink + CommentResponse: + $ref: ./schemas/common.yaml#/CommentResponse + McpAuditRequest: + $ref: ./schemas/mcp.yaml#/McpAuditRequest + ProjectStatus: + $ref: ./schemas/catalog.yaml#/ProjectStatus + Project: + $ref: ./schemas/projects.yaml#/Project + GroupResponse: + $ref: ./schemas/users.yaml#/GroupResponse + GroupMemberResponse: + $ref: ./schemas/users.yaml#/GroupMemberResponse + Notification: + $ref: ./schemas/projects.yaml#/Notification + NotificationPreference: + $ref: ./schemas/projects.yaml#/NotificationPreference + MemberResponse: + $ref: ./schemas/projects.yaml#/MemberResponse + ProjectReviewersResponse: + $ref: ./schemas/projects.yaml#/ProjectReviewersResponse + EffectivePermissions: + $ref: ./schemas/projects.yaml#/EffectivePermissions + CustomFieldDefinition: + $ref: ./schemas/projects.yaml#/CustomFieldDefinition + CustomFieldDefinitionPayload: + $ref: ./schemas/projects.yaml#/CustomFieldDefinitionPayload + SavedView: + $ref: ./schemas/projects.yaml#/SavedView + SavedViewPayload: + $ref: ./schemas/projects.yaml#/SavedViewPayload + DashboardProject: + $ref: ./schemas/projects.yaml#/DashboardProject + AttentionItem: + $ref: ./schemas/projects.yaml#/AttentionItem + Applicability: + $ref: ./schemas/catalog.yaml#/Applicability + NewApplicability: + $ref: ./schemas/catalog.yaml#/NewApplicability + Category: + $ref: ./schemas/catalog.yaml#/Category + NewCategory: + $ref: ./schemas/catalog.yaml#/NewCategory + VerificationMethod: + $ref: ./schemas/verifications.yaml#/VerificationMethod + NewVerificationMethod: + $ref: ./schemas/verifications.yaml#/NewVerificationMethod + RequirementStatus: + $ref: ./schemas/catalog.yaml#/RequirementStatus + NewRequirementStatus: + $ref: ./schemas/catalog.yaml#/NewRequirementStatus + VerificationStatus: + $ref: ./schemas/catalog.yaml#/VerificationStatus + NewVerificationStatus: + $ref: ./schemas/catalog.yaml#/NewVerificationStatus + UserCreateRequest: + $ref: ./schemas/users.yaml#/UserCreateRequest + UpdateUser: + $ref: ./schemas/users.yaml#/UpdateUser + BackupInfo: + $ref: ./schemas/admin.yaml#/BackupInfo + LogChangeDetail: + $ref: ./schemas/admin.yaml#/LogChangeDetail + AdminLogItem: + $ref: ./schemas/admin.yaml#/AdminLogItem + AdminLogList: + $ref: ./schemas/admin.yaml#/AdminLogList + LogStatsResponse: + $ref: ./schemas/admin.yaml#/LogStatsResponse + AttachmentView: + $ref: ./schemas/attachments.yaml#/AttachmentView + AttachmentStorage: + $ref: ./schemas/attachments.yaml#/AttachmentStorage + AuthProfileUpdate: + $ref: ./schemas/auth.yaml#/AuthProfileUpdate + AuthIdentities: + $ref: ./schemas/auth.yaml#/AuthIdentities + AuthChangePassword: + $ref: ./schemas/auth.yaml#/AuthChangePassword + AuthStatus: + $ref: ./schemas/auth.yaml#/AuthStatus + CacheStats: + $ref: ./schemas/admin.yaml#/CacheStats + CacheRecommendations: + $ref: ./schemas/admin.yaml#/CacheRecommendations + CacheActionResult: + $ref: ./schemas/admin.yaml#/CacheActionResult + CacheCleanupResult: + $ref: ./schemas/admin.yaml#/CacheCleanupResult + external_ProviderDiscovery_response: + $ref: ./schemas/auth.yaml#/external_ProviderDiscovery_response + external_PublicAuthProvider_response: + $ref: ./schemas/auth.yaml#/external_PublicAuthProvider_response + entities_User_response: + $ref: ./schemas/auth.yaml#/entities_User_response + use_cases_UseCaseRow_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_UseCaseRow_response + use_cases_NextCode_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_NextCode_response + use_cases_UseCaseDetail_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_UseCaseDetail_response + use_cases_VersionView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_VersionView_response + use_cases_VersionSummary_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_VersionSummary_response + use_cases_DraftSummary_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_DraftSummary_response + use_cases_DraftView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_DraftView_response + use_cases_PutDraftRequest_request: + $ref: ./schemas/use-case-views.yaml#/use_cases_PutDraftRequest_request + use_cases_DraftChangeView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_DraftChangeView_response + use_cases_SetApprovalRequest_request: + $ref: ./schemas/use-case-views.yaml#/use_cases_SetApprovalRequest_request + use_cases_LinkView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_LinkView_response + use_cases_ClearSuspectResponse_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_ClearSuspectResponse_response + use_cases_UseCaseRef_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_UseCaseRef_response + use_cases_UseCaseCommentView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_UseCaseCommentView_response + use_cases_BaselineUseCaseView_response: + $ref: ./schemas/use-case-views.yaml#/use_cases_BaselineUseCaseView_response + ApiError: + $ref: ./schemas/common.yaml#/ApiError + BuildInfo: + $ref: ./schemas/common.yaml#/BuildInfo + UseCaseElement: + $ref: ./schemas/use-cases.yaml#/UseCaseElement + UseCaseBranchEnd: + $ref: ./schemas/use-cases.yaml#/UseCaseBranchEnd + UseCaseDocument: + $ref: ./schemas/use-cases.yaml#/UseCaseDocument + UseCaseBranch: + $ref: ./schemas/use-cases.yaml#/UseCaseBranch + UseCaseEffect: + $ref: ./schemas/use-cases.yaml#/UseCaseEffect + DeploymentInfo: + $ref: ./schemas/common.yaml#/DeploymentInfo + McpPrincipalResponse: + $ref: ./schemas/mcp.yaml#/McpPrincipalResponse + UseCaseDocumentResponse: + $ref: ./schemas/use-cases.yaml#/UseCaseDocumentResponse + UseCaseBranchResponse: + $ref: ./schemas/use-cases.yaml#/UseCaseBranchResponse + UseCaseListRef: + $ref: ./schemas/use-cases.yaml#/UseCaseListRef + UseCaseNewElement: + $ref: ./schemas/use-cases.yaml#/UseCaseNewElement + UseCaseNewBranch: + $ref: ./schemas/use-cases.yaml#/UseCaseNewBranch + UseCaseDependents: + $ref: ./schemas/use-cases.yaml#/UseCaseDependents + UseCaseOperation: + $ref: ./schemas/use-cases.yaml#/UseCaseOperation + UseCaseDiffSide: + $ref: ./schemas/use-cases.yaml#/UseCaseDiffSide + UseCaseElementDiff: + $ref: ./schemas/use-cases.yaml#/UseCaseElementDiff + UseCaseDiffEnd: + $ref: ./schemas/use-cases.yaml#/UseCaseDiffEnd + UseCaseDiff: + $ref: ./schemas/use-cases.yaml#/UseCaseDiff + UseCasePublishResponse: + $ref: ./schemas/use-cases.yaml#/UseCasePublishResponse + responses: + ApiError: + $ref: ./components/responses.yaml#/ApiError +security: + - cookieAuth: [] diff --git a/docs/developer/openapi/schemas/admin.yaml b/docs/developer/openapi/schemas/admin.yaml new file mode 100644 index 00000000..a13d9614 --- /dev/null +++ b/docs/developer/openapi/schemas/admin.yaml @@ -0,0 +1,229 @@ +BackupInfo: + type: object + properties: + attachments_available: + type: boolean + attachment_files: + type: integer + minimum: 0 + attachment_bytes: + type: integer + minimum: 0 + required: + - attachments_available + - attachment_files + - attachment_bytes + additionalProperties: true +LogChangeDetail: + type: object + properties: + field: + type: string + old_value: + type: string + new_value: + type: string + required: + - field + - old_value + - new_value + additionalProperties: true +AdminLogItem: + type: object + properties: + log_id: + type: integer + user_id: + type: integer + username: + type: string + action_type: + type: string + summary: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + created_at: + type: string + changes: + type: array + items: + $ref: ../schemas/admin.yaml#/LogChangeDetail + entity_type: + type: string + entity_id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + anyOf: + - {type: integer} + - {type: 'null'} + required: + - log_id + - user_id + - username + - action_type + - summary + - description + - created_at + - changes + - entity_type + - entity_id + - project_id + additionalProperties: true +AdminLogList: + type: object + properties: + items: + type: array + items: + $ref: ../schemas/admin.yaml#/AdminLogItem + total: + type: integer + limit: + type: integer + offset: + type: integer + required: + - items + - total + - limit + - offset + additionalProperties: true +LogStatsResponse: + type: object + properties: + since: + type: string + until: + type: string + total: + type: integer + active_users: + type: integer + by_day: + type: array + items: + type: object + properties: {day: {type: string, format: date}, count: {type: integer}} + required: [day, count] + additionalProperties: true + by_action: + type: array + items: + type: object + properties: {action_type: {type: string}, count: {type: integer}} + required: [action_type, count] + additionalProperties: true + by_user: + type: array + items: + type: object + properties: {user_id: {type: integer}, username: {type: string}, count: {type: integer}} + required: [user_id, username, count] + additionalProperties: true + required: + - since + - until + - total + - active_users + - by_day + - by_action + - by_user + additionalProperties: true +CacheStats: + type: object + properties: + total_entries: + type: integer + minimum: 0 + active_entries: + type: integer + minimum: 0 + expired_entries: + type: integer + minimum: 0 + hits: + type: integer + minimum: 0 + misses: + type: integer + minimum: 0 + total_requests: + type: integer + minimum: 0 + average_access_time_ns: + type: integer + minimum: 0 + total_access_time_ns: + type: integer + minimum: 0 + cache_size_bytes: + type: integer + minimum: 0 + hit_rate: + type: number + miss_rate: + type: number + last_cleanup: + type: string + format: date-time + required: + - total_entries + - active_entries + - expired_entries + - hits + - misses + - total_requests + - average_access_time_ns + - total_access_time_ns + - cache_size_bytes + - hit_rate + - miss_rate + - last_cleanup + additionalProperties: true +CacheRecommendations: + type: object + properties: + recommendations: + type: array + items: + type: string + priority: + type: string + enum: + - high + - medium + - low + analysis_timestamp: + type: string + format: date-time + required: + - recommendations + - priority + - analysis_timestamp + additionalProperties: true +CacheActionResult: + type: object + properties: + message: + type: string + timestamp: + type: string + format: date-time + required: + - message + - timestamp + additionalProperties: true +CacheCleanupResult: + allOf: + - $ref: ../schemas/admin.yaml#/CacheActionResult + - type: object + properties: + cleaned_entries: {type: integer, minimum: 0} + required: + - cleaned_entries + additionalProperties: true diff --git a/docs/developer/openapi/schemas/attachments.yaml b/docs/developer/openapi/schemas/attachments.yaml new file mode 100644 index 00000000..4f8a64df --- /dev/null +++ b/docs/developer/openapi/schemas/attachments.yaml @@ -0,0 +1,70 @@ +AttachmentView: + type: object + properties: + id: + type: integer + entity_type: + type: string + enum: + - requirement + - verification + entity_id: + type: integer + filename: + type: string + content_type: + type: string + size_bytes: + type: integer + uploaded_by: + type: + - integer + - 'null' + uploaded_by_name: + type: + - string + - 'null' + created_at: + type: string + deleted: + type: boolean + required: + - id + - entity_type + - entity_id + - filename + - content_type + - size_bytes + - uploaded_by + - uploaded_by_name + - created_at + - deleted + additionalProperties: true +AttachmentStorage: + type: object + properties: + used_bytes: + type: integer + quota_bytes: + type: integer + quota_is_default: + type: boolean + default_quota_bytes: + type: integer + retained_by_baselines_bytes: + type: integer + max_file_bytes: + type: integer + allowed_extensions: + type: array + items: + type: string + required: + - used_bytes + - quota_bytes + - quota_is_default + - default_quota_bytes + - retained_by_baselines_bytes + - max_file_bytes + - allowed_extensions + additionalProperties: true diff --git a/docs/developer/openapi/schemas/auth.yaml b/docs/developer/openapi/schemas/auth.yaml new file mode 100644 index 00000000..ce935f24 --- /dev/null +++ b/docs/developer/openapi/schemas/auth.yaml @@ -0,0 +1,141 @@ +OAuthTokenRequest: + oneOf: + - type: object + properties: + client_id: {type: string} + resource: {type: string} + grant_type: {type: string, const: authorization_code} + code: {type: string} + redirect_uri: {type: string} + code_verifier: {type: string} + refresh_token: {type: string} + required: + - grant_type + - client_id + - resource + - code + - redirect_uri + - code_verifier + additionalProperties: true + - type: object + properties: + client_id: {type: string} + resource: {type: string} + grant_type: {type: string, const: refresh_token} + refresh_token: {type: string} + code: {type: string} + redirect_uri: {type: string} + code_verifier: {type: string} + required: + - grant_type + - client_id + - resource + - refresh_token + additionalProperties: true +AuthProfileUpdate: + type: object + properties: + name: + type: string + email: + type: string + current_password: + type: + - string + - 'null' + required: + - name + - email + additionalProperties: true +AuthIdentities: + type: object + properties: + password_configured: + type: boolean + identities: + type: array + items: + type: object + properties: {id: {type: integer}, provider: {type: string}, created_at: {type: string}, last_login_at: {type: [string, 'null']}} + required: [id, provider, created_at, last_login_at] + additionalProperties: true + required: + - password_configured + - identities + additionalProperties: true +AuthChangePassword: + type: object + properties: + current_password: + type: string + new_password: + type: string + confirm_password: + type: string + required: + - current_password + - new_password + - confirm_password + additionalProperties: true +AuthStatus: + type: object + properties: + status: + type: string + const: ok + required: + - status + additionalProperties: true +external_ProviderDiscovery_response: + type: object + properties: + password_enabled: + type: boolean + external: + type: array + items: + $ref: ../schemas/auth.yaml#/external_PublicAuthProvider_response + additionalProperties: true + required: + - password_enabled + - external +external_PublicAuthProvider_response: + type: object + properties: + id: + type: string + display_name: + type: string + additionalProperties: true + required: + - id + - display_name +entities_User_response: + type: object + properties: + id: + type: integer + username: + type: string + name: + type: string + email: + type: string + creation_date: + type: string + last_login: + type: string + is_admin: + type: boolean + email_verified: + type: boolean + additionalProperties: true + required: + - id + - username + - name + - email + - creation_date + - last_login + - is_admin + - email_verified diff --git a/docs/developer/openapi/schemas/baselines.yaml b/docs/developer/openapi/schemas/baselines.yaml new file mode 100644 index 00000000..a115b5ae --- /dev/null +++ b/docs/developer/openapi/schemas/baselines.yaml @@ -0,0 +1,289 @@ +Baseline: + type: object + properties: + id: + type: integer + project_id: + type: integer + created_by: + type: integer + name: + type: string + created_at: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + source_saved_view_id: + anyOf: + - {type: integer} + - {type: 'null'} + source_view_definition: + anyOf: + - {} + - {type: 'null'} + required: + - id + - project_id + - created_by + - name + - created_at + - description + - source_saved_view_id + - source_view_definition + additionalProperties: true +BaselineTraceability: + type: object + properties: + baseline_id: + type: integer + requirement_id: + type: integer + verification_id: + type: integer + suspect: + type: boolean + suspect_at: + anyOf: + - {type: string} + - {type: 'null'} + suspect_reason: + anyOf: + - {type: string} + - {type: 'null'} + required: + - baseline_id + - requirement_id + - verification_id + - suspect + - suspect_at + - suspect_reason + additionalProperties: true +BaselineVerification: + type: object + properties: + status_id: + type: integer + project_id: + type: integer + author_id: + type: integer + reviewer_id: + type: integer + name: + type: string + reference_code: + type: string + description: + type: string + source: + type: string + parent_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_method_id: + anyOf: + - {type: integer} + - {type: 'null'} + baseline_id: + type: integer + verification_id: + type: integer + required: + - status_id + - project_id + - author_id + - reviewer_id + - name + - reference_code + - description + - source + - parent_id + - verification_method_id + - baseline_id + - verification_id + additionalProperties: true +BaselineDiff: + type: object + properties: + requirements_only_in_a: + type: array + items: + type: integer + requirements_only_in_b: + type: array + items: + type: integer + trace_only_in_a: + type: array + items: + type: array + items: {type: integer} + minItems: 2 + maxItems: 2 + trace_only_in_b: + type: array + items: + type: array + items: {type: integer} + minItems: 2 + maxItems: 2 + use_cases_only_in_a: + type: array + items: + type: integer + use_cases_only_in_b: + type: array + items: + type: integer + use_cases_changed: + type: array + items: + type: object + properties: {use_case_id: {type: integer}, version_a: {type: integer}, version_b: {type: integer}} + required: [use_case_id, version_a, version_b] + additionalProperties: true + required: + - requirements_only_in_a + - requirements_only_in_b + - trace_only_in_a + - trace_only_in_b + - use_cases_only_in_a + - use_cases_only_in_b + - use_cases_changed + additionalProperties: true +TextDiffResult: + type: object + properties: + added: + type: array + items: + type: string + removed: + type: array + items: + type: string + unchanged: + type: array + items: + type: string + required: + - added + - removed + - unchanged + additionalProperties: true +SingleValueDiff: + type: object + properties: + old_id: + type: integer + new_id: + type: integer + unchanged: + type: integer + unchanged_label: + type: string + old_label: + type: string + new_label: + type: string + required: [] + additionalProperties: true +VerificationDiff: + type: object + properties: + added_ids: + type: array + items: + type: integer + removed_ids: + type: array + items: + type: integer + unchanged_ids: + type: array + items: + type: integer + added_labels: + type: array + items: + type: string + removed_labels: + type: array + items: + type: string + unchanged_labels: + type: array + items: + type: string + required: + - added_ids + - removed_ids + - unchanged_ids + additionalProperties: true +RequirementDiff: + type: object + properties: + text: + type: object + properties: + title: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + description: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + justification: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + required: + - title + - description + - justification + additionalProperties: true + metadata: + type: object + properties: + status: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + category: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + applicability: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + verification: {$ref: ../schemas/baselines.yaml#/VerificationDiff} + custom_fields: {type: array, items: {type: object, properties: {field_id: {type: integer}, label: {type: string}, old_value: {anyOf: [{type: string}, {type: 'null'}]}, new_value: {anyOf: [{type: string}, {type: 'null'}]}, unchanged: {type: boolean}}, required: [field_id, label, old_value, new_value, unchanged], additionalProperties: true}} + required: + - status + - category + - applicability + - verification + - custom_fields + additionalProperties: true + required: + - text + - metadata + additionalProperties: true +VerificationVersionDiff: + type: object + properties: + text: + type: object + properties: + name: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + description: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + source: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + reference_code: {$ref: ../schemas/baselines.yaml#/TextDiffResult} + required: + - name + - description + - source + - reference_code + additionalProperties: true + metadata: + type: object + properties: + status: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + verification_method: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + parent: {$ref: ../schemas/baselines.yaml#/SingleValueDiff} + required: + - status + - verification_method + - parent + additionalProperties: true + required: + - text + - metadata + additionalProperties: true diff --git a/docs/developer/openapi/schemas/catalog.yaml b/docs/developer/openapi/schemas/catalog.yaml new file mode 100644 index 00000000..4664ca96 --- /dev/null +++ b/docs/developer/openapi/schemas/catalog.yaml @@ -0,0 +1,211 @@ +ProjectStatus: + type: string + enum: + - Active + - Completed + - OnHold + - Cancelled +Applicability: + type: object + properties: + id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - id + - project_id + - title + - description + - tag + additionalProperties: true +NewApplicability: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - title + - description + - tag + - project_id + additionalProperties: true +Category: + type: object + properties: + id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - id + - project_id + - title + - description + - tag + additionalProperties: true +NewCategory: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - title + - description + - tag + - project_id + additionalProperties: true +RequirementStatus: + type: object + properties: + id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + is_system: + type: boolean + tag_color: + anyOf: + - {type: string} + - {type: 'null'} + required: + - id + - project_id + - title + - description + - tag + - is_system + - tag_color + additionalProperties: true +NewRequirementStatus: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + is_system: + type: boolean + tag_color: + anyOf: + - {type: string} + - {type: 'null'} + required: + - title + - description + - tag + - project_id + additionalProperties: true +VerificationStatus: + type: object + properties: + id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + is_system: + type: boolean + tag_color: + anyOf: + - {type: string} + - {type: 'null'} + outcome: + type: string + enum: + - passed + - failed + - in_progress + - not_run + required: + - id + - project_id + - title + - description + - tag + - is_system + - tag_color + - outcome + additionalProperties: true +NewVerificationStatus: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + is_system: + type: boolean + tag_color: + anyOf: + - {type: string} + - {type: 'null'} + outcome: + anyOf: + - {type: string, enum: [passed, failed, in_progress, not_run]} + - {type: 'null'} + required: + - title + - description + - tag + - project_id + additionalProperties: true diff --git a/docs/developer/openapi/schemas/common.yaml b/docs/developer/openapi/schemas/common.yaml new file mode 100644 index 00000000..9ca9027b --- /dev/null +++ b/docs/developer/openapi/schemas/common.yaml @@ -0,0 +1,155 @@ +ExcelImportResult: + type: object + properties: + success: + type: boolean + message: + type: string + imported_count: + type: integer + errors: + type: array + items: + type: string + imported_requirement_ids: + type: array + items: + type: integer + required: + - success + - message + - imported_count + - errors + - imported_requirement_ids + additionalProperties: true +EntityActivityItem: + type: object + properties: + log_id: + type: integer + user_id: + type: integer + username: + type: string + action_type: + type: string + summary: + type: string + created_at: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + changes: + type: array + items: + $ref: ../schemas/admin.yaml#/LogChangeDetail + required: + - log_id + - user_id + - username + - action_type + - summary + - created_at + - description + - changes + additionalProperties: true +CommentResponse: + type: object + properties: + id: + type: integer + requirement_id: + type: integer + author_id: + type: integer + requirement_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + author_name: + type: string + body: + type: string + created_at: + type: string + required: + - id + - requirement_id + - author_id + - requirement_version_id + - author_name + - body + - created_at + additionalProperties: true +ApiError: + type: object + required: + - status + - error + - message + properties: + status: + type: integer + error: + type: string + message: + type: string + additionalProperties: true +BuildInfo: + type: object + properties: + backend_version: + type: string + backend_git_sha: + type: string + deployment_mode: + type: string + enum: + - server + - cloud + frontend_compatibility: + type: object + properties: + min_version: {type: string} + max_version: {type: string} + required: + - min_version + - max_version + additionalProperties: true + required: + - backend_version + - backend_git_sha + - deployment_mode + - frontend_compatibility + additionalProperties: true +DeploymentInfo: + type: object + properties: + mode: + type: string + enum: + - server + - cloud + allows_self_registration: + type: boolean + requires_email_verification: + type: boolean + allows_admin_promotion: + type: boolean + assigns_personal_workspace: + type: boolean + allows_self_administered_user_creation: + type: boolean + allows_database_backup: + type: boolean + required: + - mode + - allows_self_registration + - requires_email_verification + - allows_admin_promotion + - assigns_personal_workspace + - allows_self_administered_user_creation + - allows_database_backup + additionalProperties: true diff --git a/docs/developer/openapi/schemas/mcp.yaml b/docs/developer/openapi/schemas/mcp.yaml new file mode 100644 index 00000000..dfd47072 --- /dev/null +++ b/docs/developer/openapi/schemas/mcp.yaml @@ -0,0 +1,82 @@ +McpAuditRequest: + type: object + properties: + project_id: + anyOf: + - {type: integer} + - {type: 'null'} + session_id: + anyOf: + - {type: string} + - {type: 'null'} + tool_name: + type: string + enum: + - list_projects + - get_requirement + - list_requirements + - get_versions + - semantic_search_requirements + - compare_versions + - trace_up + - trace_down + - coverage_report + - dependency_matrix + - get_baseline + - diff_baselines + - list_verifications + - get_verification + - list_baselines + - get_requirement_activity + - get_verification_activity + - list_requirement_comments + - get_verification_matrix + - list_project_catalog + - diff_baseline_vs_current + - create_requirement + - patch_requirement + - set_approval + - create_baseline + - create_requirement_comment + - create_verification + - update_verification + - put_verification_matrix + - clear_suspect + params_summary: + anyOf: + - {type: string} + - {type: 'null'} + result_summary: + anyOf: + - {type: string} + - {type: 'null'} + is_write: + type: boolean + required: + - tool_name + - is_write + additionalProperties: true +McpPrincipalResponse: + type: object + properties: + user_id: + type: integer + authentication_type: + type: string + principal_id: + type: string + client_id: + anyOf: + - {type: string} + - {type: 'null'} + grant_id: + anyOf: + - {type: integer} + - {type: 'null'} + additionalProperties: true + required: + - user_id + - authentication_type + - principal_id + - client_id + - grant_id diff --git a/docs/developer/openapi/schemas/projects.yaml b/docs/developer/openapi/schemas/projects.yaml new file mode 100644 index 00000000..990e7510 --- /dev/null +++ b/docs/developer/openapi/schemas/projects.yaml @@ -0,0 +1,458 @@ +Project: + type: object + properties: + id: + type: integer + name: + type: string + slug: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + creation_date: + anyOf: + - {type: string} + - {type: 'null'} + update_date: + anyOf: + - {type: string} + - {type: 'null'} + archived_at: + anyOf: + - {type: string} + - {type: 'null'} + owner_id: + anyOf: + - {type: integer} + - {type: 'null'} + group_id: + anyOf: + - {type: integer} + - {type: 'null'} + archived_by: + anyOf: + - {type: integer} + - {type: 'null'} + status: + $ref: ../schemas/catalog.yaml#/ProjectStatus + required: + - id + - name + - slug + - description + - creation_date + - update_date + - archived_at + - owner_id + - group_id + - archived_by + - status + additionalProperties: true +Notification: + type: object + properties: + id: + type: integer + user_id: + type: integer + project_id: + anyOf: + - {type: integer} + - {type: 'null'} + entity_id: + anyOf: + - {type: integer} + - {type: 'null'} + actor_id: + anyOf: + - {type: integer} + - {type: 'null'} + notification_type: + type: string + title: + type: string + created_at: + type: string + body: + anyOf: + - {type: string} + - {type: 'null'} + entity_type: + anyOf: + - {type: string} + - {type: 'null'} + read: + type: boolean + emailed: + type: boolean + required: + - id + - user_id + - project_id + - entity_id + - actor_id + - notification_type + - title + - created_at + - body + - entity_type + - read + - emailed + additionalProperties: true +NotificationPreference: + type: object + properties: + id: + type: integer + user_id: + type: integer + project_id: + type: integer + notify_in_app: + type: boolean + notify_email: + type: boolean + required: + - id + - user_id + - project_id + - notify_in_app + - notify_email + additionalProperties: true +MemberResponse: + type: object + properties: + user_id: + type: integer + role: + type: integer + role_label: + type: string + username: + type: string + name: + type: string + required: + - user_id + - role + - role_label + - username + - name + additionalProperties: true +ProjectReviewersResponse: + type: object + properties: + user_ids: + type: array + items: + type: integer + required: + - user_ids + additionalProperties: true +EffectivePermissions: + type: object + properties: + view_requirements: + type: boolean + edit_requirements: + type: boolean + approve_versions: + type: boolean + is_project_reviewer: + type: boolean + manage_custom_fields: + type: boolean + manage_project_configuration: + type: boolean + manage_project_members: + type: boolean + archived: + type: boolean + required: + - view_requirements + - edit_requirements + - approve_versions + - is_project_reviewer + - manage_custom_fields + - manage_project_configuration + - manage_project_members + - archived + additionalProperties: true +CustomFieldDefinition: + type: object + properties: + id: + type: integer + project_id: + type: integer + label: + type: string + field_type: + type: string + enum: + - text + - enum + - boolean + - number + enum_values: + anyOf: + - {} + - {type: 'null'} + sort_order: + type: integer + created_at: + type: string + required: + - id + - project_id + - label + - field_type + - enum_values + - sort_order + - created_at + additionalProperties: true +CustomFieldDefinitionPayload: + type: object + properties: + label: + type: string + field_type: + type: string + enum: + - text + - enum + - boolean + - number + enum_values: + anyOf: + - {type: array, items: {type: string}} + - {type: 'null'} + sort_order: + anyOf: + - {type: integer} + - {type: 'null'} + required: + - label + - field_type + additionalProperties: true +SavedView: + type: object + properties: + id: + type: integer + project_id: + type: integer + owner_id: + type: integer + name: + type: string + visibility: + type: string + enum: + - private + - shared + created_at: + type: string + updated_at: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + locked_at: + anyOf: + - {type: string} + - {type: 'null'} + locked: + type: boolean + definition: {} + required: + - id + - project_id + - owner_id + - name + - visibility + - created_at + - updated_at + - description + - locked_at + - locked + - definition + additionalProperties: true +SavedViewPayload: + type: object + properties: + name: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + visibility: + type: string + enum: + - private + - shared + definition: {} + required: + - name + - visibility + - definition + additionalProperties: true +DashboardProject: + type: object + properties: + project_id: + type: integer + requirements_count: + type: integer + tests_count: + type: integer + project_slug: + type: string + project_base_path: + type: string + name: + type: string + status_id: + type: string + project_status_normalized: + type: string + project_status_badge: + type: string + project_initial: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + creation_date: + anyOf: + - {type: string} + - {type: 'null'} + update_date: + anyOf: + - {type: string} + - {type: 'null'} + archived_at: + anyOf: + - {type: string} + - {type: 'null'} + project_owner_name: + anyOf: + - {type: string} + - {type: 'null'} + group_name: + anyOf: + - {type: string} + - {type: 'null'} + group_slug: + anyOf: + - {type: string} + - {type: 'null'} + group_path: + anyOf: + - {type: string} + - {type: 'null'} + role_label: + anyOf: + - {type: string} + - {type: 'null'} + owner_id: + anyOf: + - {type: integer} + - {type: 'null'} + group_id: + anyOf: + - {type: integer} + - {type: 'null'} + role_id: + anyOf: + - {type: integer} + - {type: 'null'} + archived: + type: boolean + can_view_group: + type: boolean + required: + - project_id + - requirements_count + - tests_count + - project_slug + - project_base_path + - name + - status_id + - project_status_normalized + - project_status_badge + - project_initial + - description + - creation_date + - update_date + - archived_at + - project_owner_name + - group_name + - group_slug + - group_path + - role_label + - owner_id + - group_id + - role_id + - archived + - can_view_group + additionalProperties: true +AttentionItem: + type: object + properties: + project_id: + type: integer + title: + type: string + requirement_id: + anyOf: + - {type: integer} + - {type: 'null'} + use_case_id: + anyOf: + - {type: integer} + - {type: 'null'} + count: + anyOf: + - {type: integer} + - {type: 'null'} + notification_id: + anyOf: + - {type: integer} + - {type: 'null'} + reference_code: + anyOf: + - {type: string} + - {type: 'null'} + notification_type: + anyOf: + - {type: string} + - {type: 'null'} + at: + anyOf: + - {type: string} + - {type: 'null'} + kind: + type: string + enum: + - approval + - review + - suspect + - notification + required: + - project_id + - title + - requirement_id + - use_case_id + - count + - notification_id + - reference_code + - notification_type + - at + - kind + additionalProperties: true diff --git a/docs/developer/openapi/schemas/reports.yaml b/docs/developer/openapi/schemas/reports.yaml new file mode 100644 index 00000000..ee6e2a2a --- /dev/null +++ b/docs/developer/openapi/schemas/reports.yaml @@ -0,0 +1,213 @@ +ReportDefinition: + type: object + properties: + report_type: + type: string + enum: + - vcd + - coverage + document: + type: object + properties: + doc_id: {type: string} + title: {type: string} + issue: {type: string} + revision: {type: string} + classification: {type: string} + watermark: {anyOf: [{type: string}, {type: 'null'}]} + page_size: {type: string, enum: [a4, letter]} + pdf_a: {type: boolean} + signatories: {type: array, items: {type: object, properties: {role: {type: string}, name: {type: string}}, required: [role, name], additionalProperties: true}} + change_record: {type: array, items: {type: object, properties: {issue: {type: string}, date: {type: string}, changes: {type: string}, author: {type: string}}, required: [issue, date, changes, author], additionalProperties: true}} + documents: {type: array, items: {type: object, properties: {ref: {type: string}, id: {type: string}, title: {type: string}, issue: {type: string}}, required: [ref, id, title, issue], additionalProperties: true}} + required: + - doc_id + - title + - issue + - revision + - classification + - watermark + - page_size + - pdf_a + - signatories + - change_record + - documents + additionalProperties: true + sections: + type: array + items: + type: object + properties: {key: {type: string}, enabled: {type: boolean}, options: {type: object, additionalProperties: true}} + required: [key, enabled] + additionalProperties: true + required: + - report_type + - document + - sections + additionalProperties: true +ReportDefinitionInput: + type: object + properties: + report_type: + type: string + enum: + - vcd + - coverage + document: + type: object + properties: + doc_id: {type: string} + title: {type: string} + issue: {type: string} + revision: {type: string} + classification: {type: string} + watermark: {anyOf: [{type: string}, {type: 'null'}]} + page_size: {type: string, enum: [a4, letter]} + pdf_a: {type: boolean} + signatories: {type: array, items: {type: object, properties: {role: {type: string}, name: {type: string}}, required: [], additionalProperties: false}} + change_record: {type: array, items: {type: object, properties: {issue: {type: string}, date: {type: string}, changes: {type: string}, author: {type: string}}, required: [], additionalProperties: false}} + documents: {type: array, items: {type: object, properties: {ref: {type: string}, id: {type: string}, title: {type: string}, issue: {type: string}}, required: [], additionalProperties: false}} + required: [] + additionalProperties: false + sections: + type: array + items: + type: object + properties: {key: {type: string}, enabled: {type: boolean}, options: {type: object, additionalProperties: {}}} + required: [key] + additionalProperties: false + required: + - report_type + additionalProperties: false +ReportOptionSpec: + oneOf: + - type: object + properties: + key: {type: string} + label: {type: string} + default: {} + kind: {type: string, const: select} + choices: {type: array, items: {type: object, properties: {value: {type: string}, label: {type: string}}, required: [value, label], additionalProperties: true}} + required: + - key + - label + - default + - kind + - choices + additionalProperties: true + - type: object + properties: + key: {type: string} + label: {type: string} + default: {} + kind: {type: string, const: multi_select} + choices: {type: array, items: {type: object, properties: {value: {type: string}, label: {type: string}}, required: [value, label], additionalProperties: true}} + required: + - key + - label + - default + - kind + - choices + additionalProperties: true + - type: object + properties: + key: {type: string} + label: {type: string} + default: {} + kind: {type: string, const: text} + multiline: {type: boolean} + required: + - key + - label + - default + - kind + - multiline + additionalProperties: true +ReportSectionSpec: + type: object + properties: + key: + type: string + title: + type: string + description: + type: string + report_types: + type: array + items: + type: string + enum: [vcd, coverage] + default_enabled: + type: boolean + options: + type: array + items: + $ref: ../schemas/reports.yaml#/ReportOptionSpec + required: + - key + - title + - description + - report_types + - default_enabled + - options + additionalProperties: true +ReportTemplateView: + type: object + properties: + id: + type: integer + project_id: + type: integer + owner_id: + type: integer + name: + type: string + report_type: + type: string + enum: + - vcd + - coverage + visibility: + type: string + enum: + - private + - shared + owner_name: + type: string + created_at: + type: string + updated_at: + type: string + can_edit: + type: boolean + definition: {} + required: + - id + - project_id + - owner_id + - name + - report_type + - visibility + - owner_name + - created_at + - updated_at + - can_edit + - definition + additionalProperties: true +ReportTemplateInput: + type: object + properties: + name: + anyOf: + - {type: string} + - {type: 'null'} + visibility: + anyOf: + - {type: string} + - {type: 'null'} + definition: + anyOf: + - {$ref: ../schemas/reports.yaml#/ReportDefinitionInput} + - {type: 'null'} + required: [] + additionalProperties: false diff --git a/docs/developer/openapi/schemas/requirements.yaml b/docs/developer/openapi/schemas/requirements.yaml new file mode 100644 index 00000000..a01bb384 --- /dev/null +++ b/docs/developer/openapi/schemas/requirements.yaml @@ -0,0 +1,376 @@ +SemanticSearchResult: + type: object + properties: + id: + type: integer + rank: + type: integer + lexical_rank: + type: integer + vector_rank: + type: integer + reference_code: + type: string + title: + type: string + description: + type: string + snippet: + type: string + status: + type: string + category: + type: string + applicability: + type: string + verification: + type: string + score: + type: number + required: + - id + - rank + - reference_code + - title + - description + - snippet + - status + - category + - applicability + - verification + - score + additionalProperties: true +RagAnswerResponse: + type: object + properties: + answer: + type: string + citations: + type: array + items: + type: object + properties: {requirement_id: {type: integer}, reference_code: {type: string}, title: {type: string}} + required: [requirement_id, reference_code, title] + additionalProperties: true + results: + type: array + items: + $ref: ../schemas/requirements.yaml#/SemanticSearchResult + required: + - answer + - citations + - results + additionalProperties: true +CustomFieldValueDisplay: + type: object + properties: + field_id: + type: integer + label: + type: string + value: + anyOf: + - {type: string} + - {type: 'null'} + required: + - field_id + - label + - value + additionalProperties: true +Requirement: + type: object + properties: + id: + type: integer + status_id: + type: integer + author_id: + type: integer + reviewer_id: + type: integer + category_id: + type: integer + applicability_id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + reference_code: + type: string + creation_date: + type: string + update_date: + type: string + current_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + parent_id: + anyOf: + - {type: integer} + - {type: 'null'} + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + deadline_date: + anyOf: + - {type: string} + - {type: 'null'} + justification: + anyOf: + - {type: string} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + approval_state: + type: string + enum: + - draft + - reviewed + - approved + same_as_current: + type: boolean + custom_fields: + type: array + items: + $ref: ../schemas/requirements.yaml#/CustomFieldValueDisplay + required: + - id + - status_id + - author_id + - reviewer_id + - category_id + - applicability_id + - project_id + - title + - description + - reference_code + - creation_date + - update_date + - current_version_id + - parent_id + - approved_by + - deadline_date + - justification + - approved_at + - approval_state + additionalProperties: true +RequirementVersion: + type: object + properties: + id: + type: integer + requirement_id: + type: integer + status_id: + type: integer + author_id: + type: integer + reviewer_id: + type: integer + category_id: + type: integer + applicability_id: + type: integer + title: + type: string + description: + type: string + created_at: + type: string + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + reviewed_by: + anyOf: + - {type: integer} + - {type: 'null'} + justification: + anyOf: + - {type: string} + - {type: 'null'} + deadline_date: + anyOf: + - {type: string} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + reviewed_at: + anyOf: + - {type: string} + - {type: 'null'} + approval_state: + type: string + enum: + - draft + - reviewed + - approved + required: + - id + - requirement_id + - status_id + - author_id + - reviewer_id + - category_id + - applicability_id + - title + - description + - created_at + - approved_by + - reviewed_by + - justification + - deadline_date + - approved_at + - reviewed_at + - approval_state + additionalProperties: true +RequirementListRow: + allOf: + - $ref: ../schemas/requirements.yaml#/Requirement + - type: object + properties: + verification_method_ids: {type: array, items: {type: integer}} + parent_requirement_ids: {type: array, items: {type: integer}} + required: + - verification_method_ids + - parent_requirement_ids + additionalProperties: true +RequirementWithTraceSummary: + allOf: + - $ref: ../schemas/requirements.yaml#/Requirement + - type: object + properties: + trace_summary: {type: object, properties: {parent_links: {type: array, items: {$ref: ../schemas/traceability.yaml#/RequirementVersionLink}}, child_ids: {type: array, items: {type: integer}}, linked_test_ids: {type: array, items: {type: integer}}}, required: [parent_links, child_ids, linked_test_ids], additionalProperties: true} + required: + - trace_summary + additionalProperties: true +RequirementVersionDetail: + allOf: + - $ref: ../schemas/requirements.yaml#/RequirementVersion + - type: object + properties: + custom_fields: {type: array, items: {$ref: ../schemas/requirements.yaml#/CustomFieldValueDisplay}} + verification_method_ids: {type: array, items: {type: integer}} + required: + - custom_fields + - verification_method_ids + additionalProperties: true +CustomFieldValueInput: + type: object + properties: + field_id: + type: integer + value: + anyOf: + - {type: string} + - {type: 'null'} + required: + - field_id + additionalProperties: true +RequirementCreateRequest: + type: object + properties: + title: + type: string + description: + type: string + reference_code: + type: string + author_id: + type: integer + category_id: + type: integer + status_id: + type: integer + reviewer_id: + type: integer + applicability_id: + type: integer + project_id: + type: integer + justification: + anyOf: + - {type: string} + - {type: 'null'} + verification_method_ids: + type: array + items: + type: integer + minItems: 1 + custom_fields: + type: array + items: + $ref: ../schemas/requirements.yaml#/CustomFieldValueInput + parent_links: + type: array + items: + type: object + properties: {target_version_id: {type: integer}, link_type: {type: string}, rationale: {anyOf: [{type: string}, {type: 'null'}]}} + required: [target_version_id, link_type] + additionalProperties: true + required: + - title + - description + - reference_code + - author_id + - category_id + - status_id + - reviewer_id + - applicability_id + - project_id + - verification_method_ids + additionalProperties: true +RequirementPatch: + type: object + properties: + title: + anyOf: + - {type: string} + - {type: 'null'} + description: + anyOf: + - {type: string} + - {type: 'null'} + justification: + anyOf: + - {type: string} + - {type: 'null'} + status_id: + anyOf: + - {type: integer} + - {type: 'null'} + author_id: + anyOf: + - {type: integer} + - {type: 'null'} + reviewer_id: + anyOf: + - {type: integer} + - {type: 'null'} + category_id: + anyOf: + - {type: integer} + - {type: 'null'} + applicability_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_method_ids: + anyOf: + - {type: array, items: {type: integer}} + - {type: 'null'} + custom_fields: + anyOf: + - {type: array, items: {$ref: ../schemas/requirements.yaml#/CustomFieldValueInput}} + - {type: 'null'} + required: [] + additionalProperties: true diff --git a/docs/developer/openapi/schemas/traceability.yaml b/docs/developer/openapi/schemas/traceability.yaml new file mode 100644 index 00000000..e6edd787 --- /dev/null +++ b/docs/developer/openapi/schemas/traceability.yaml @@ -0,0 +1,252 @@ +Dsm: + type: object + properties: + order: + type: string + link_types: + type: array + items: + type: string + requirements: + type: array + items: + type: object + properties: {id: {type: integer}, index: {type: integer}, category_id: {type: integer}, depth: {type: integer}, reference_code: {type: string}, title: {type: string}, approval_state: {type: string}, category: {type: string}, parent_id: {anyOf: [{type: integer}, {type: 'null'}]}} + required: [id, index, category_id, depth, reference_code, title, approval_state, category, parent_id] + additionalProperties: true + cells: + type: array + items: + type: object + properties: {row: {type: integer}, col: {type: integer}, link_types: {type: array, items: {type: string}}, link_ids: {type: array, items: {type: integer}}, upstream_changed: {type: boolean}, in_loop: {type: boolean}} + required: [row, col, link_types, link_ids, upstream_changed, in_loop] + additionalProperties: true + groups: + type: array + items: + type: object + properties: {label: {type: string}, start: {type: integer}, end: {type: integer}} + required: [label, start, end] + additionalProperties: true + loops: + type: array + items: + type: object + properties: {requirement_ids: {type: array, items: {type: integer}}, path: {type: array, items: {type: integer}}} + required: [requirement_ids, path] + additionalProperties: true + stats: + type: object + properties: + requirements: {type: integer} + links: {type: integer} + cells: {type: integer} + loops: {type: integer} + upstream_changed: {type: integer} + external_links: {type: integer} + required: + - requirements + - links + - cells + - loops + - upstream_changed + - external_links + additionalProperties: true + required: + - order + - link_types + - requirements + - cells + - groups + - loops + - stats + additionalProperties: true +MatrixLink: + type: object + properties: + req_id: + type: integer + verification_id: + type: integer + project_id: + type: integer + creation_date: + type: string + suspect: + type: boolean + suspect_at: + anyOf: + - {type: string} + - {type: 'null'} + suspect_reason: + anyOf: + - {type: string} + - {type: 'null'} + cleared_at: + anyOf: + - {type: string} + - {type: 'null'} + cleared_by: + anyOf: + - {type: integer} + - {type: 'null'} + triggering_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + triggering_user_id: + anyOf: + - {type: integer} + - {type: 'null'} + required: + - req_id + - verification_id + - project_id + - creation_date + - suspect + - suspect_at + - suspect_reason + - cleared_at + - cleared_by + - triggering_version_id + - triggering_user_id + additionalProperties: true +VerificationControlView: + type: object + properties: + verification_id: + type: integer + verification_level: + anyOf: + - {type: string} + - {type: 'null'} + verification_stage: + anyOf: + - {type: string} + - {type: 'null'} + evidence_reference: + anyOf: + - {type: string} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_at: + anyOf: + - {type: string} + - {type: 'null'} + suggestions: + type: object + properties: + levels: {type: array, items: {type: string}} + stages: {type: array, items: {type: string}} + required: + - levels + - stages + additionalProperties: true + required: + - verification_id + - verification_level + - verification_stage + - evidence_reference + - updated_by + - updated_at + - suggestions + additionalProperties: true +RequirementCloseOutView: + type: object + properties: + requirement_id: + type: integer + compliance: + anyOf: + - {type: string, enum: [C, PC, NC]} + - {type: 'null'} + note: + anyOf: + - {type: string} + - {type: 'null'} + set_by: + anyOf: + - {type: integer} + - {type: 'null'} + set_at: + anyOf: + - {type: string} + - {type: 'null'} + close_out: + type: object + properties: + status: {type: string, enum: [open, closed]} + reason: {type: string} + required: + - status + - reason + additionalProperties: true + verifications: + type: array + items: + type: object + properties: {id: {type: integer}, reference_code: {type: string}, name: {type: string}, status_id: {type: integer}, status_title: {type: string}, outcome: {type: string, enum: [passed, failed, in_progress, not_run]}} + required: [id, reference_code, name, status_id, status_title, outcome] + additionalProperties: true + required: + - requirement_id + - compliance + - note + - set_by + - set_at + - close_out + - verifications + additionalProperties: true +VerificationControlInput: + type: object + properties: + verification_level: + anyOf: + - {type: string} + - {type: 'null'} + verification_stage: + anyOf: + - {type: string} + - {type: 'null'} + evidence_reference: + anyOf: + - {type: string} + - {type: 'null'} + required: [] + additionalProperties: false +RequirementVersionLink: + type: object + properties: + id: + type: integer + source_version_id: + type: integer + target_version_id: + type: integer + project_id: + type: integer + link_type: + type: string + created_at: + type: string + rationale: + anyOf: + - {type: string} + - {type: 'null'} + metadata: + anyOf: + - {} + - {type: 'null'} + required: + - id + - source_version_id + - target_version_id + - project_id + - link_type + - created_at + - rationale + - metadata + additionalProperties: true diff --git a/docs/developer/openapi/schemas/use-case-views.yaml b/docs/developer/openapi/schemas/use-case-views.yaml new file mode 100644 index 00000000..6286722f --- /dev/null +++ b/docs/developer/openapi/schemas/use-case-views.yaml @@ -0,0 +1,545 @@ +use_cases_UseCaseRow_response: + type: object + properties: + id: + type: integer + reference_code: + type: string + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + step_count: + type: integer + alternative_count: + type: integer + exception_count: + type: integer + approval_state: + anyOf: + - {type: string} + - {type: 'null'} + current_version_number: + anyOf: + - {type: integer} + - {type: 'null'} + has_draft: + type: boolean + link_count: + type: integer + suspect_count: + type: integer + updated_at: + type: string + additionalProperties: true + required: + - id + - reference_code + - title + - primary_actor + - step_count + - alternative_count + - exception_count + - approval_state + - current_version_number + - has_draft + - link_count + - suspect_count + - updated_at +use_cases_NextCode_response: + type: object + properties: + reference_code: + type: string + additionalProperties: true + required: + - reference_code +use_cases_UseCaseDetail_response: + type: object + properties: + id: + type: integer + project_id: + type: integer + reference_code: + type: string + title: + type: string + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_at: + type: string + updated_at: + type: string + current_version: + anyOf: + - {$ref: ../schemas/use-case-views.yaml#/use_cases_VersionView_response} + - {type: 'null'} + draft: + anyOf: + - {$ref: ../schemas/use-case-views.yaml#/use_cases_DraftSummary_response} + - {type: 'null'} + additionalProperties: true + required: + - id + - project_id + - reference_code + - title + - created_by + - created_at + - updated_at + - current_version + - draft +use_cases_VersionView_response: + type: object + properties: + id: + type: integer + version_number: + type: integer + approval_state: + type: string + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + change_note: + anyOf: + - {type: string} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_by_name: + anyOf: + - {type: string} + - {type: 'null'} + created_at: + type: string + reviewed_by: + anyOf: + - {type: integer} + - {type: 'null'} + reviewed_at: + anyOf: + - {type: string} + - {type: 'null'} + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + document: + $ref: ../schemas/use-cases.yaml#/UseCaseDocumentResponse + labels: + type: object + additionalProperties: + type: string + additionalProperties: true + required: + - id + - version_number + - approval_state + - title + - primary_actor + - change_note + - created_by + - created_by_name + - created_at + - reviewed_by + - reviewed_at + - approved_by + - approved_at + - document + - labels +use_cases_VersionSummary_response: + type: object + properties: + id: + type: integer + version_number: + type: integer + approval_state: + type: string + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + change_note: + anyOf: + - {type: string} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_by_name: + anyOf: + - {type: string} + - {type: 'null'} + created_at: + type: string + reviewed_by: + anyOf: + - {type: integer} + - {type: 'null'} + reviewed_at: + anyOf: + - {type: string} + - {type: 'null'} + approved_by: + anyOf: + - {type: integer} + - {type: 'null'} + approved_at: + anyOf: + - {type: string} + - {type: 'null'} + additionalProperties: true + required: + - id + - version_number + - approval_state + - title + - primary_actor + - change_note + - created_by + - created_by_name + - created_at + - reviewed_by + - reviewed_at + - approved_by + - approved_at +use_cases_DraftSummary_response: + type: object + properties: + revision: + type: integer + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: + type: string + additionalProperties: true + required: + - revision + - base_version_id + - updated_by + - updated_by_name + - updated_at +use_cases_DraftView_response: + type: object + properties: + use_case_id: + type: integer + revision: + type: integer + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: + type: string + document: + $ref: ../schemas/use-cases.yaml#/UseCaseDocumentResponse + labels: + type: object + additionalProperties: + type: string + additionalProperties: true + required: + - use_case_id + - revision + - base_version_id + - updated_by + - updated_by_name + - updated_at + - document + - labels +use_cases_PutDraftRequest_request: + type: object + properties: + base_revision: + anyOf: + - {type: integer} + - {type: 'null'} + document: + $ref: ../schemas/use-cases.yaml#/UseCaseDocument + additionalProperties: true + required: + - document +use_cases_DraftChangeView_response: + type: object + properties: + use_case_id: + type: integer + revision: + type: integer + base_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by: + anyOf: + - {type: integer} + - {type: 'null'} + updated_by_name: + anyOf: + - {type: string} + - {type: 'null'} + updated_at: + type: string + document: + $ref: ../schemas/use-cases.yaml#/UseCaseDocumentResponse + labels: + type: object + additionalProperties: + type: string + effects: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseEffect + additionalProperties: true + required: + - use_case_id + - revision + - base_version_id + - updated_by + - updated_by_name + - updated_at + - document + - labels + - effects +use_cases_SetApprovalRequest_request: + type: object + properties: + state: + type: string + additionalProperties: true + required: + - state +use_cases_LinkView_response: + type: object + properties: + id: + type: integer + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + target_type: + type: string + enum: + - requirement + - verification + requirement_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_id: + anyOf: + - {type: integer} + - {type: 'null'} + target_reference_code: + type: string + target_title: + type: string + rationale: + anyOf: + - {type: string} + - {type: 'null'} + suspect: + type: boolean + suspect_at: + anyOf: + - {type: string} + - {type: 'null'} + suspect_reason: + anyOf: + - {type: string} + - {type: 'null'} + linked_requirement_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + current_requirement_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + created_by: + anyOf: + - {type: integer} + - {type: 'null'} + created_at: + type: string + additionalProperties: true + required: + - id + - element_id + - element_label + - target_type + - requirement_id + - verification_id + - target_reference_code + - target_title + - rationale + - suspect + - suspect_at + - suspect_reason + - linked_requirement_version_id + - current_requirement_version_id + - created_by + - created_at +use_cases_ClearSuspectResponse_response: + type: object + properties: + status: + type: string + cleared: + type: boolean + additionalProperties: true + required: + - status + - cleared +use_cases_UseCaseRef_response: + type: object + properties: + use_case_id: + type: integer + reference_code: + type: string + title: + type: string + link_id: + type: integer + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + suspect: + type: boolean + additionalProperties: true + required: + - use_case_id + - reference_code + - title + - link_id + - element_id + - element_label + - suspect +use_cases_UseCaseCommentView_response: + type: object + properties: + id: + type: integer + use_case_id: + type: integer + use_case_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + element_id: + anyOf: + - {type: string} + - {type: 'null'} + element_label: + anyOf: + - {type: string} + - {type: 'null'} + author_id: + type: integer + author_name: + type: string + body: + type: string + created_at: + type: string + additionalProperties: true + required: + - id + - use_case_id + - use_case_version_id + - element_id + - element_label + - author_id + - author_name + - body + - created_at +use_cases_BaselineUseCaseView_response: + type: object + properties: + use_case_id: + type: integer + reference_code: + type: string + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + version_id: + type: integer + version_number: + type: integer + approval_state: + type: string + current_version_id: + anyOf: + - {type: integer} + - {type: 'null'} + current_version_number: + anyOf: + - {type: integer} + - {type: 'null'} + link_count: + type: integer + additionalProperties: true + required: + - use_case_id + - reference_code + - title + - primary_actor + - version_id + - version_number + - approval_state + - current_version_id + - current_version_number + - link_count diff --git a/docs/developer/openapi/schemas/use-cases.yaml b/docs/developer/openapi/schemas/use-cases.yaml new file mode 100644 index 00000000..f2733aef --- /dev/null +++ b/docs/developer/openapi/schemas/use-cases.yaml @@ -0,0 +1,501 @@ +UseCaseElement: + type: object + properties: + id: + type: string + text: + type: string + additionalProperties: false + required: + - id + - text +UseCaseBranchEnd: + oneOf: + - type: object + properties: + type: {const: rejoin, type: string} + step: {type: string} + additionalProperties: false + required: + - type + - step + - type: object + properties: + type: {const: end, type: string} + outcome: {type: string, enum: [success, failure]} + additionalProperties: false + required: + - type + - outcome +UseCaseDocument: + type: object + properties: + schema: + type: string + const: marreq.use-case.v1 + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + secondary_actors: + type: array + items: + type: string + summary: + anyOf: + - {type: string} + - {type: 'null'} + preconditions: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + main: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + branches: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseBranch + postconditions: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + additionalProperties: false + required: + - title +UseCaseBranch: + type: object + properties: + id: + type: string + kind: + type: string + enum: + - alternative + - exception + title: + type: string + from_step: + type: string + end: + $ref: ../schemas/use-cases.yaml#/UseCaseBranchEnd + steps: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + additionalProperties: false + required: + - id + - kind + - title + - from_step + - end +UseCaseEffect: + oneOf: + - type: object + properties: + type: {type: string, const: relabelled} + id: {type: string} + from: {type: string} + to: {type: string} + additionalProperties: true + required: + - type + - id + - from + - to + - type: object + properties: + type: {type: string, const: removed} + id: {type: string} + additionalProperties: true + required: + - type + - id + - type: object + properties: + type: {type: string, const: rebranched} + id: {type: string} + from_step: {type: string} + additionalProperties: true + required: + - type + - id + - from_step + - type: object + properties: + type: {type: string, const: created} + id: {type: string} + additionalProperties: true + required: + - type + - id +UseCaseDocumentResponse: + type: object + properties: + schema: + type: string + const: marreq.use-case.v1 + title: + type: string + primary_actor: + anyOf: + - {type: string} + - {type: 'null'} + secondary_actors: + type: array + items: + type: string + summary: + anyOf: + - {type: string} + - {type: 'null'} + preconditions: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + main: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + branches: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseBranchResponse + postconditions: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + additionalProperties: false + required: + - schema + - title + - primary_actor + - secondary_actors + - summary + - preconditions + - main + - branches + - postconditions +UseCaseBranchResponse: + type: object + properties: + id: + type: string + kind: + type: string + enum: + - alternative + - exception + title: + type: string + from_step: + type: string + end: + $ref: ../schemas/use-cases.yaml#/UseCaseBranchEnd + steps: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElement + additionalProperties: false + required: + - id + - kind + - title + - from_step + - end + - steps +UseCaseListRef: + oneOf: + - type: string + enum: + - preconditions + - main + - postconditions + - type: object + properties: + branch: {type: string} + additionalProperties: true + required: + - branch +UseCaseNewElement: + type: object + properties: + id: + anyOf: + - {type: string} + - {type: 'null'} + text: + type: string + additionalProperties: false + required: + - text +UseCaseNewBranch: + type: object + properties: + id: + anyOf: + - {type: string} + - {type: 'null'} + kind: + type: string + enum: + - alternative + - exception + title: + type: string + from_step: + type: string + end: + $ref: ../schemas/use-cases.yaml#/UseCaseBranchEnd + steps: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseNewElement + additionalProperties: false + required: + - kind + - title + - from_step + - end +UseCaseDependents: + oneOf: + - type: string + enum: + - cascade + - type: object + properties: + rebranch_to: {type: string} + additionalProperties: true + required: + - rebranch_to +UseCaseOperation: + oneOf: + - type: object + properties: + op: {type: string, const: set_header} + title: {anyOf: [{type: string}, {type: 'null'}]} + primary_actor: {anyOf: [{type: string}, {type: 'null'}]} + secondary_actors: {anyOf: [{type: array, items: {type: string}}, {type: 'null'}]} + summary: {anyOf: [{type: string}, {type: 'null'}]} + additionalProperties: false + required: + - op + - type: object + properties: + op: {type: string, const: add_element} + list: {$ref: ../schemas/use-cases.yaml#/UseCaseListRef} + after: {anyOf: [{type: string}, {type: 'null'}]} + element: {$ref: ../schemas/use-cases.yaml#/UseCaseNewElement} + additionalProperties: false + required: + - op + - list + - element + - type: object + properties: + op: {type: string, const: update_text} + id: {type: string} + text: {type: string} + additionalProperties: false + required: + - op + - id + - text + - type: object + properties: + op: {type: string, const: move_element} + id: {type: string} + list: {anyOf: [{$ref: ../schemas/use-cases.yaml#/UseCaseListRef}, {type: 'null'}]} + after: {anyOf: [{type: string}, {type: 'null'}]} + additionalProperties: false + required: + - op + - id + - type: object + properties: + op: {type: string, const: remove_element} + id: {type: string} + dependents: {anyOf: [{$ref: ../schemas/use-cases.yaml#/UseCaseDependents}, {type: 'null'}]} + additionalProperties: false + required: + - op + - id + - type: object + properties: + op: {type: string, const: add_branch} + branch: {$ref: ../schemas/use-cases.yaml#/UseCaseNewBranch} + additionalProperties: false + required: + - op + - branch + - type: object + properties: + op: {type: string, const: update_branch} + id: {type: string} + title: {anyOf: [{type: string}, {type: 'null'}]} + kind: {anyOf: [{type: string, enum: [alternative, exception]}, {type: 'null'}]} + from_step: {anyOf: [{type: string}, {type: 'null'}]} + end: {anyOf: [{$ref: ../schemas/use-cases.yaml#/UseCaseBranchEnd}, {type: 'null'}]} + additionalProperties: false + required: + - op + - id + - type: object + properties: + op: {type: string, const: remove_branch} + id: {type: string} + additionalProperties: false + required: + - op + - id +UseCaseDiffSide: + type: object + properties: + list: + type: string + enum: + - preconditions + - main + - postconditions + - branch + - branches + branch_id: + type: string + label: + anyOf: + - {type: string} + - {type: 'null'} + text: + type: string + from_step: + type: string + end: + $ref: ../schemas/use-cases.yaml#/UseCaseBranchEnd + kind: + type: string + enum: + - alternative + - exception + additionalProperties: true + required: + - list + - label + - text +UseCaseElementDiff: + type: object + properties: + id: + type: string + kind: + type: string + enum: + - precondition + - postcondition + - step + - branch + changes: + type: array + items: + type: string + enum: [added, removed, edited, moved, rebranched] + old: + anyOf: + - {$ref: ../schemas/use-cases.yaml#/UseCaseDiffSide} + - {type: 'null'} + new: + anyOf: + - {$ref: ../schemas/use-cases.yaml#/UseCaseDiffSide} + - {type: 'null'} + text_diff: + type: array + items: + type: object + properties: {op: {type: string, enum: [equal, delete, insert]}, text: {type: string}} + additionalProperties: true + required: [op, text] + additionalProperties: true + required: + - id + - kind + - changes + - old + - new +UseCaseDiffEnd: + type: object + properties: + version_id: + anyOf: + - {type: integer} + - {type: 'null'} + version_number: + anyOf: + - {type: integer} + - {type: 'null'} + draft_revision: + anyOf: + - {type: integer} + - {type: 'null'} + additionalProperties: true + required: + - version_id + - version_number + - draft_revision +UseCaseDiff: + type: object + properties: + from: + anyOf: + - {$ref: ../schemas/use-cases.yaml#/UseCaseDiffEnd} + - {type: 'null'} + to: + $ref: ../schemas/use-cases.yaml#/UseCaseDiffEnd + summary: + type: object + properties: + added: {type: integer} + removed: {type: integer} + edited: {type: integer} + moved: {type: integer} + rebranched: {type: integer} + additionalProperties: true + required: + - added + - removed + - edited + - moved + - rebranched + header: + type: object + additionalProperties: + type: object + properties: {old: {oneOf: [{type: string}, {type: 'null'}, {type: array, items: {type: string}}]}, new: {oneOf: [{type: string}, {type: 'null'}, {type: array, items: {type: string}}]}} + additionalProperties: true + required: [old, new] + elements: + type: array + items: + $ref: ../schemas/use-cases.yaml#/UseCaseElementDiff + additionalProperties: true + required: + - from + - to + - summary + - header + - elements +UseCasePublishResponse: + type: object + properties: + version: + $ref: ../schemas/use-case-views.yaml#/use_cases_VersionView_response + removed_links: + type: array + items: + $ref: ../schemas/use-case-views.yaml#/use_cases_LinkView_response + additionalProperties: true + required: + - version + - removed_links diff --git a/docs/developer/openapi/schemas/users.yaml b/docs/developer/openapi/schemas/users.yaml new file mode 100644 index 00000000..d35eaf6f --- /dev/null +++ b/docs/developer/openapi/schemas/users.yaml @@ -0,0 +1,85 @@ +GroupResponse: + type: object + properties: + id: + type: integer + name: + type: string + slug: + type: string + description: + anyOf: + - {type: string} + - {type: 'null'} + owner_id: + anyOf: + - {type: integer} + - {type: 'null'} + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - slug + - description + - owner_id + - created_at + - updated_at + additionalProperties: true +GroupMemberResponse: + type: object + properties: + user_id: + type: integer + role: + type: integer + role_label: + type: string + required: + - user_id + - role + - role_label + additionalProperties: true +UserCreateRequest: + type: object + properties: + username: + type: string + name: + type: string + email: + type: string + password: + type: string + is_admin: + type: boolean + required: + - username + - name + - email + - password + - is_admin + additionalProperties: true +UpdateUser: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + username: + type: string + name: + type: string + email: + type: string + is_admin: + type: boolean + required: + - username + - name + - email + - is_admin + additionalProperties: true diff --git a/docs/developer/openapi/schemas/verifications.yaml b/docs/developer/openapi/schemas/verifications.yaml new file mode 100644 index 00000000..c3048b7c --- /dev/null +++ b/docs/developer/openapi/schemas/verifications.yaml @@ -0,0 +1,134 @@ +Verification: + type: object + properties: + id: + type: integer + status_id: + type: integer + project_id: + type: integer + author_id: + type: integer + reviewer_id: + type: integer + name: + type: string + reference_code: + type: string + description: + type: string + source: + type: string + parent_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_method_id: + anyOf: + - {type: integer} + - {type: 'null'} + status_set_by: + anyOf: + - {type: integer} + - {type: 'null'} + status_set_at: + anyOf: + - {type: string} + - {type: 'null'} + required: + - id + - status_id + - project_id + - author_id + - reviewer_id + - name + - reference_code + - description + - source + - parent_id + - verification_method_id + - status_set_by + - status_set_at + additionalProperties: true +NewVerification: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + status_id: + type: integer + project_id: + type: integer + author_id: + type: integer + reviewer_id: + type: integer + name: + type: string + reference_code: + type: string + description: + type: string + source: + type: string + parent_id: + anyOf: + - {type: integer} + - {type: 'null'} + verification_method_id: + anyOf: + - {type: integer} + - {type: 'null'} + required: + - status_id + - project_id + - author_id + - reviewer_id + - name + - reference_code + - description + - source + additionalProperties: true +VerificationMethod: + type: object + properties: + id: + type: integer + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - id + - project_id + - title + - description + - tag + additionalProperties: true +NewVerificationMethod: + type: object + properties: + id: + anyOf: + - {type: integer} + - {type: 'null'} + project_id: + type: integer + title: + type: string + description: + type: string + tag: + type: string + required: + - title + - description + - tag + - project_id + additionalProperties: true diff --git a/marreq-cloud/tests/api_contract_inventory.rs b/marreq-cloud/tests/api_contract_inventory.rs index ffd647db..8def3b23 100644 --- a/marreq-cloud/tests/api_contract_inventory.rs +++ b/marreq-cloud/tests/api_contract_inventory.rs @@ -3,11 +3,15 @@ //! Additional, unconsumed backend routes deliberately do not fail this check. use std::collections::BTreeSet; -fn spec() -> serde_json::Value { - marreq_contracts::contract::load( - &std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../docs/developer/openapi.yaml"), - ) - .unwrap() +fn spec() -> &'static serde_json::Value { + static SPEC: std::sync::OnceLock = std::sync::OnceLock::new(); + SPEC.get_or_init(|| { + marreq_contracts::source::load( + &std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../docs/developer/openapi/root.yaml"), + ) + .unwrap() + }) } #[test] @@ -32,14 +36,14 @@ fn consumed_contract_routes_exist_in_server_and_cloud() { }) }) .collect(); - let documented = marreq_contracts::contract::operations(&spec, Some(variant)).unwrap(); + let documented = marreq_contracts::contract::operations(spec, Some(variant)).unwrap(); for key in documented.keys() { assert!( actual.contains(&(key.method.clone(), key.path.clone())), "{variant}: missing compiled {key}" ); } - for (key, op) in marreq_contracts::contract::operations(&spec, None).unwrap() { + for (key, op) in marreq_contracts::contract::operations(spec, None).unwrap() { if op .get("x-marreq-variants") .and_then(serde_json::Value::as_array) @@ -63,14 +67,14 @@ fn audited_cache_models_match_the_wire_schemas() { ("CacheRecommendations", cache.get_recommendations()), ] { let schema = &spec["components"]["schemas"][name]; - marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + marreq_contracts::contract::validate_instance(spec, schema, &value).unwrap(); for property in schema["required"].as_array().unwrap() { let mut broken = value.clone(); broken .as_object_mut() .unwrap() .remove(property.as_str().unwrap()); - assert!(marreq_contracts::contract::validate_instance(&spec, schema, &broken).is_err()); + assert!(marreq_contracts::contract::validate_instance(spec, schema, &broken).is_err()); } } } @@ -95,7 +99,7 @@ fn audited_auth_requests_deserialize_as_the_real_rust_forms() { ), ] { marreq_contracts::contract::validate_instance( - &spec, + spec, &spec["components"]["schemas"][name], &value, ) @@ -142,17 +146,17 @@ fn attachment_dtos_preserve_nullable_fields_and_storage_limits() { ("AttachmentStorage", serde_json::to_value(storage).unwrap()), ] { let schema = &spec["components"]["schemas"][name]; - marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + marreq_contracts::contract::validate_instance(spec, schema, &value).unwrap(); let mut broken = value; let key = schema["required"][0].as_str().unwrap(); broken.as_object_mut().unwrap().remove(key); - assert!(marreq_contracts::contract::validate_instance(&spec, schema, &broken).is_err()); + assert!(marreq_contracts::contract::validate_instance(spec, schema, &broken).is_err()); } } fn request_model(operation_id: &str, value: serde_json::Value) { let spec = spec(); - let op = marreq_contracts::contract::operations(&spec, None) + let op = marreq_contracts::contract::operations(spec, None) .unwrap() .into_values() .find(|op| op["operationId"] == operation_id) @@ -160,7 +164,7 @@ fn request_model(operation_id: &str, value: serd let schema = op .pointer("/requestBody/content/application~1json/schema") .unwrap(); - marreq_contracts::contract::validate_instance(&spec, schema, &value).unwrap(); + marreq_contracts::contract::validate_instance(spec, schema, &value).unwrap(); assert!( serde_json::from_value::(value.clone()).is_ok(), "{operation_id}: real Rust request rejected its fixture" @@ -183,7 +187,7 @@ fn request_model(operation_id: &str, value: serd candidates.push(changed); } for candidate in candidates { - if marreq_contracts::contract::validate_instance(&spec, schema, &candidate).is_ok() { + if marreq_contracts::contract::validate_instance(spec, schema, &candidate).is_ok() { assert!( serde_json::from_value::(candidate).is_ok(), "{operation_id}: contract accepts an invalid Rust field {key}" @@ -283,7 +287,7 @@ fn report_defaults_and_section_catalog_match_the_contract() { let value = serde_json::to_value(ReportDefinition::default_for(kind, &context)).unwrap(); for name in ["ReportDefinition", "ReportDefinitionInput"] { marreq_contracts::contract::validate_instance( - &spec, + spec, &spec["components"]["schemas"][name], &value, ) @@ -293,7 +297,7 @@ fn report_defaults_and_section_catalog_match_the_contract() { for section in sections::catalog() { let value = serde_json::to_value(section).unwrap(); marreq_contracts::contract::validate_instance( - &spec, + spec, &spec["components"]["schemas"]["ReportSectionSpec"], &value, ) diff --git a/marreq-core/tests/it/contract.rs b/marreq-core/tests/it/contract.rs index aa86c58b..42264694 100644 --- a/marreq-core/tests/it/contract.rs +++ b/marreq-core/tests/it/contract.rs @@ -10,9 +10,9 @@ use std::{io::Cursor, sync::OnceLock}; fn spec() -> &'static Value { static SPEC: OnceLock = OnceLock::new(); SPEC.get_or_init(|| { - contract::load( + marreq_contracts::source::load( &std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../docs/developer/openapi.yaml"), + .join("../docs/developer/openapi/root.yaml"), ) .unwrap() }) diff --git a/package-lock.json b/package-lock.json index a32db7ab..8770a342 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { + "@redocly/openapi-core": "1.34.22", "@testing-library/jest-dom": "^6.9.1", "@vitest/coverage-v8": "^5.0.5", "@vitest/ui": "^5.0.2", diff --git a/package.json b/package.json index 430c99ed..41394f41 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ }, "homepage": "https://github.com/mariusmm/Marreq#readme", "devDependencies": { + "@redocly/openapi-core": "1.34.22", "@testing-library/jest-dom": "^6.9.1", "@vitest/coverage-v8": "^5.0.5", "@vitest/ui": "^5.0.2", diff --git a/scripts/bundle-openapi.mjs b/scripts/bundle-openapi.mjs new file mode 100644 index 00000000..a8553d2b --- /dev/null +++ b/scripts/bundle-openapi.mjs @@ -0,0 +1,41 @@ +// Redocly resolves OpenAPI references; this adapter enforces local-only inputs. +// Exact npm version and integrity are pinned in package.json/package-lock.json. +import { realpathSync } from 'node:fs'; +import { dirname, resolve, sep } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { BaseResolver, bundle, createConfig } from '@redocly/openapi-core'; + +export async function bundleOpenApi(input) { + const entry = realpathSync(input); + const directory = dirname(entry); + class LocalResolver extends BaseResolver { + async loadExternalRef(reference) { + if (/^[a-z][a-z\d+.-]*:/i.test(reference)) throw new Error(`Network/URI references are forbidden: ${reference}`); + const file = realpathSync(reference); + if (file !== entry && !file.startsWith(directory + sep)) throw new Error(`Reference escapes contract directory: ${reference}`); + return super.loadExternalRef(file); + } + } + const result = await bundle({ + ref: entry, + config: await createConfig({}), + externalRefResolver: new LocalResolver(), + }); + if (result.problems.length) { + throw new Error(result.problems.map(p => `${p.location?.[0]?.source?.absoluteRef ?? entry}: ${p.message}`).join('\n')); + } + // Historical comparison accepts only a self-contained JSON document. No + // resolver or network access is needed when verifying retained bundles. + function selfContained(value) { + if (!value || typeof value !== 'object') return; + if ('$ref' in value && !value.$ref.startsWith('#/')) throw new Error(`Unresolved reference: ${value.$ref}`); + for (const child of Object.values(value)) selfContained(child); + } + selfContained(result.bundle.parsed); + return JSON.stringify(result.bundle.parsed, null, 2) + '\n'; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + try { process.stdout.write(await bundleOpenApi(process.argv[2] ?? 'docs/developer/openapi/root.yaml')); } + catch (error) { console.error(error.message); process.exitCode = 1; } +} diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs index 22155b8e..17889234 100644 --- a/tools/marreq-contracts/src/artifact.rs +++ b/tools/marreq-contracts/src/artifact.rs @@ -1,5 +1,5 @@ //! Marreq artifact identity/policy. Contract hashes pin files, NOT Docker images. -use crate::{contract, coverage}; +use crate::{contract, coverage, source}; use anyhow::{Context, Result, ensure}; use semver::Version; use serde::{Deserialize, Serialize}; @@ -141,7 +141,7 @@ fn identity(bundle: &Bundle) -> Result<()> { } pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> Result { component(who)?; - let spec = contract::load(&root.join("docs/developer/openapi.yaml"))?; + let spec = source::load(&root.join(source::DEFAULT_SPEC))?; let required: BTreeSet<_> = if who == "backend" { variant(mode.as_deref().context("backend --variant required")?)?; contract::operations(&spec, mode.as_deref())? @@ -163,7 +163,10 @@ pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> } let directory = if who == "mcp" { "mcp-server" } else { who }; let mut inputs: Vec = [ - "docs/developer/openapi.yaml", + "docs/developer/openapi", + "scripts/bundle-openapi.mjs", + "package.json", + "package-lock.json", "tools/marreq-contracts", "Cargo.toml", "Cargo.lock", diff --git a/tools/marreq-contracts/src/lib.rs b/tools/marreq-contracts/src/lib.rs index d0ca8ac9..6d8db84e 100644 --- a/tools/marreq-contracts/src/lib.rs +++ b/tools/marreq-contracts/src/lib.rs @@ -1,4 +1,5 @@ pub mod artifact; pub mod contract; pub mod coverage; +pub mod source; pub mod traffic; diff --git a/tools/marreq-contracts/src/main.rs b/tools/marreq-contracts/src/main.rs index 405459a0..cc1febc1 100644 --- a/tools/marreq-contracts/src/main.rs +++ b/tools/marreq-contracts/src/main.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use marreq_contracts::{artifact, contract, coverage, traffic}; +use marreq_contracts::{artifact, contract, coverage, source, traffic}; use std::path::PathBuf; #[derive(Parser)] struct Cli { @@ -11,13 +11,13 @@ enum Action { Coverage { #[arg(long, default_value = ".")] root: PathBuf, - #[arg(long, default_value = "docs/developer/openapi.yaml")] + #[arg(long, default_value = source::DEFAULT_SPEC)] spec: PathBuf, #[arg(long)] component: String, }, Validate { - #[arg(long, default_value = "docs/developer/openapi.yaml")] + #[arg(long, default_value = source::DEFAULT_SPEC)] spec: PathBuf, #[arg(long, default_value = ".")] root: PathBuf, @@ -50,7 +50,7 @@ enum Action { }, VerifyTraffic { transcript: PathBuf, - #[arg(long, default_value = "docs/developer/openapi.yaml")] + #[arg(long, default_value = source::DEFAULT_SPEC)] spec: PathBuf, }, Select { @@ -58,7 +58,7 @@ enum Action { root: PathBuf, #[arg(long)] component: String, - #[arg(long, default_value = "docs/developer/openapi.yaml")] + #[arg(long, default_value = source::DEFAULT_SPEC)] spec: PathBuf, #[arg(long)] output: PathBuf, @@ -79,7 +79,7 @@ fn run() -> anyhow::Result { } => Ok(serde_json::to_value(coverage::scan( &root, &component, - &contract::load(&spec)?, + &source::load(&spec)?, )?)?), Action::Bundle { root, @@ -124,7 +124,7 @@ fn run() -> anyhow::Result { artifact::verify(&manifest, candidate) } Action::VerifyTraffic { transcript, spec } => { - traffic::verify(&contract::load(&spec)?, &transcript) + traffic::verify(&source::load(&spec)?, &transcript) } Action::Select { root, @@ -132,7 +132,7 @@ fn run() -> anyhow::Result { spec, output, } => { - let spec = contract::load(&spec)?; + let spec = source::load(&spec)?; let coverage = coverage::scan(&root, &component, &spec)?; contract::write( &output, @@ -147,7 +147,7 @@ fn run() -> anyhow::Result { root, document_only, } => { - let spec = contract::load(&spec)?; + let spec = source::load(&spec)?; contract::validate(&spec)?; for dir in ["frontend", "mcp-server"] { let p: artifact::Policy = serde_json::from_value(contract::load( diff --git a/tools/marreq-contracts/src/source.rs b/tools/marreq-contracts/src/source.rs new file mode 100644 index 00000000..d6880a3f --- /dev/null +++ b/tools/marreq-contracts/src/source.rs @@ -0,0 +1,22 @@ +//! Load editable OpenAPI sources through the pinned, offline Redocly bundler. +//! Retained artifact documents use `contract::load` and never run a resolver. +use anyhow::{Context, Result, ensure}; +use serde_json::Value; +use std::{path::Path, process::Command}; + +pub const DEFAULT_SPEC: &str = "docs/developer/openapi/root.yaml"; + +pub fn load(path: &Path) -> Result { + let output = Command::new("node") + .arg(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../scripts/bundle-openapi.mjs")) + .arg(path) + .output() + .context("OpenAPI bundling requires Node 22+ and `npm ci` at repository root")?; + ensure!( + output.status.success(), + "OpenAPI bundling failed for {}: {}", + path.display(), + String::from_utf8_lossy(&output.stderr) + ); + serde_json::from_slice(&output.stdout).context("invalid bundled OpenAPI JSON") +} diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs index f1f2c91e..6b8eae44 100644 --- a/tools/marreq-contracts/tests/compatibility.rs +++ b/tools/marreq-contracts/tests/compatibility.rs @@ -595,13 +595,14 @@ fn browser_calls_and_assignments_cannot_hide_dependencies() { fn consumer_bundle_requires_committed_build_configuration() { let dir = tempfile::tempdir().unwrap(); let root = dir.path(); - std::fs::create_dir_all(root.join("docs/developer")).unwrap(); + std::fs::create_dir_all(root.join("docs/developer/openapi")).unwrap(); std::fs::create_dir_all(root.join("frontend/src")).unwrap(); - std::fs::copy( - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/developer/openapi.yaml"), - root.join("docs/developer/openapi.yaml"), - ) - .unwrap(); + contract::write( + &root.join(marreq_contracts::source::DEFAULT_SPEC), + &marreq_contracts::source::load( + &Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/developer/openapi/root.yaml"), + ).unwrap(), + ).unwrap(); std::fs::write( root.join("frontend/src/client.ts"), "fetch('/api/meta/build');", From c38f5c684bc6d5092dfb22d80fd457645b45fc80 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:40:19 +0100 Subject: [PATCH 12/18] refactor(contracts): separate syntax, transports, routes and consumer analysis --- tools/marreq-contracts/src/artifact.rs | 57 +- tools/marreq-contracts/src/coverage.rs | 844 ------------------ .../marreq-contracts/src/coverage/analyzer.rs | 266 ++++++ tools/marreq-contracts/src/coverage/mod.rs | 118 +++ tools/marreq-contracts/src/coverage/routes.rs | 88 ++ tools/marreq-contracts/src/coverage/syntax.rs | 303 +++++++ .../src/coverage/transports.rs | 136 +++ tools/marreq-contracts/src/main.rs | 28 +- 8 files changed, 960 insertions(+), 880 deletions(-) delete mode 100644 tools/marreq-contracts/src/coverage.rs create mode 100644 tools/marreq-contracts/src/coverage/analyzer.rs create mode 100644 tools/marreq-contracts/src/coverage/mod.rs create mode 100644 tools/marreq-contracts/src/coverage/routes.rs create mode 100644 tools/marreq-contracts/src/coverage/syntax.rs create mode 100644 tools/marreq-contracts/src/coverage/transports.rs diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs index 17889234..c6cda30d 100644 --- a/tools/marreq-contracts/src/artifact.rs +++ b/tools/marreq-contracts/src/artifact.rs @@ -46,6 +46,39 @@ pub struct FrontendRange { pub min_version: Version, pub max_version: Version, } +impl FrontendRange { + pub fn validate(&self) -> Result<()> { + ensure!( + self.min_version.cmp_precedence(&self.max_version).is_le(), + "invalid backend frontend range" + ); + Ok(()) + } + pub fn accepts(&self, version: &Version) -> Result<()> { + self.validate()?; + ensure!( + version.cmp_precedence(&self.min_version).is_ge() + && version.cmp_precedence(&self.max_version).is_le(), + "frontend version outside backend policy" + ); + Ok(()) + } +} + +/// Validate editable component policies; retained bundles carry their own policy. +pub fn validate_source_policies(root: &Path) -> Result<()> { + for directory in ["frontend", "mcp-server"] { + let policy: Policy = serde_json::from_value(contract::load( + &root.join(directory).join("compatibility.json"), + )?)?; + policy.validate()?; + } + let range: FrontendRange = serde_json::from_value(contract::load( + &root.join("marreq-core/frontend_compatibility.json"), + )?)?; + range.validate() +} + #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Bundle { @@ -118,10 +151,11 @@ fn identity(bundle: &Bundle) -> Result<()> { .as_deref() .context("backend variant required")?, )?; - ensure!( - bundle.frontend_compatibility.is_some(), - "backend frontend policy missing" - ); + bundle + .frontend_compatibility + .as_ref() + .context("backend frontend policy missing")? + .validate()?; } else { ensure!( bundle.variant.is_none(), @@ -296,16 +330,11 @@ pub fn verify(path: &Path, candidate: Option<(&str, &Version, &str, &Path)>) -> "plan does not reference the source-exported tagged candidate" ); } - let range = backend.frontend_compatibility.as_ref().unwrap(); - ensure!( - range.min_version.cmp_precedence(&range.max_version).is_le(), - "invalid backend frontend range" - ); - ensure!( - frontend.version.cmp_precedence(&range.min_version).is_ge() - && frontend.version.cmp_precedence(&range.max_version).is_le(), - "frontend version outside backend policy" - ); + backend + .frontend_compatibility + .as_ref() + .unwrap() + .accepts(&frontend.version)?; for consumer in [&frontend, &mcp] { consumer .compatibility diff --git a/tools/marreq-contracts/src/coverage.rs b/tools/marreq-contracts/src/coverage.rs deleted file mode 100644 index 52d609e8..00000000 --- a/tools/marreq-contracts/src/coverage.rs +++ /dev/null @@ -1,844 +0,0 @@ -//! Conservative TypeScript call analysis using the real TS/TSX grammar. -//! Unknown URL or HTTP-method expressions fail instead of dropping dependencies. -use crate::contract::{Operation, operations}; -use anyhow::{Context, Result, bail}; -use serde::Serialize; -use serde_json::Value; -use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, - path::Path, -}; -use tree_sitter::Node; - -#[derive(Debug, Serialize)] -pub struct Coverage { - pub operations: BTreeSet, - pub call_sites: usize, -} - -fn children(node: Node<'_>) -> Vec> { - let mut cursor = node.walk(); - node.named_children(&mut cursor).collect() -} -fn descend<'a>(node: Node<'a>, kind: &str, found: &mut Vec>) { - if node.kind() == kind { - found.push(node); - } - for child in children(node) { - descend(child, kind, found); - } -} -fn text<'a>(node: Node<'_>, source: &'a str) -> &'a str { - &source[node.byte_range()] -} -fn field<'a>(node: Node<'a>, name: &str) -> Result> { - node.child_by_field_name(name).context("missing AST field") -} -fn name(node: Node<'_>, source: &str) -> String { - if matches!( - node.kind(), - "await_expression" | "parenthesized_expression" | "instantiation_expression" - ) && let Some(inner) = children(node).first() - { - return name(*inner, source); - } - if node.kind() == "subscript_expression" - && let Some(index) = node - .child_by_field_name("index") - .filter(|n| n.kind() == "string") - { - return text(index, source).trim_matches(['\'', '"']).into(); - } - node.child_by_field_name("property") - .map_or_else(|| text(node, source), |n| text(n, source)) - .to_owned() -} -fn enclosing(mut node: Node<'_>) -> Node<'_> { - while let Some(parent) = node.parent() { - if matches!( - node.kind(), - "function_declaration" | "method_definition" | "arrow_function" - ) { - return node; - } - node = parent; - } - node -} -fn combine(left: Vec, right: Vec) -> Vec { - left.into_iter() - .flat_map(|a| right.iter().map(move |b| format!("{a}{b}"))) - .collect() -} - -// Only finite string construction is evaluated. Unknown parameter values become -// a segment wildcard; an entirely unknown endpoint or HTTP method is rejected. -fn values( - node: Node<'_>, - source: &str, - root: Node<'_>, - bound: &BTreeMap>, - depth: usize, -) -> Vec { - if depth > 16 { - return vec!["*".into()]; - } - let eval = |n| values(n, source, root, bound, depth + 1); - match node.kind() { - "string" => vec![text(node, source)[1..text(node, source).len() - 1].to_owned()], - "template_string" => { - let mut result = vec![String::new()]; - for child in children(node) { - let part = if child.kind() == "string_fragment" { - vec![text(child, source).to_owned()] - } else if child.kind() == "template_substitution" { - children(child) - .first() - .map_or(vec!["*".into()], |n| eval(*n)) - } else { - vec![text(child, source).to_owned()] - }; - result = combine(result, part); - if result.len() > 128 { - return vec!["*".into()]; - } - } - result - } - "ternary_expression" => ["consequence", "alternative"] - .into_iter() - .filter_map(|f| node.child_by_field_name(f)) - .flat_map(eval) - .collect(), - "binary_expression" - if node - .child_by_field_name("operator") - .is_some_and(|n| text(n, source) == "+") => - { - combine( - eval(field(node, "left").unwrap()), - eval(field(node, "right").unwrap()), - ) - } - "identifier" => { - let id = text(node, source); - if let Some(v) = bound.get(id) { - return v.clone(); - } - let scope = enclosing(node); - let mut vars = vec![]; - descend(scope, "variable_declarator", &mut vars); - if let Some(value) = vars - .into_iter() - .filter(|v| { - v.start_byte() < node.start_byte() - && v.parent() - .and_then(|p| p.child(0)) - .is_some_and(|n| n.kind() == "const") - }) - .filter(|v| { - let mut block = v.parent(); - while block.is_some_and(|p| !matches!(p.kind(), "statement_block" | "program")) - { - block = block.and_then(|p| p.parent()); - } - let mut parent = Some(node); - while let Some(p) = parent { - if Some(p) == block { - return true; - } - parent = p.parent(); - } - false - }) - .rev() - .find_map(|v| { - (v.child_by_field_name("name") - .is_some_and(|n| text(n, source) == id)) - .then(|| v.child_by_field_name("value")) - .flatten() - }) - { - return eval(value); - } - if let Some(parameters) = scope.child_by_field_name("parameters") { - for parameter in children(parameters) { - let ident = parameter - .child_by_field_name("pattern") - .or_else(|| parameter.child_by_field_name("name")); - let numeric = parameter.child_by_field_name("type").is_some_and(|t| { - text(t, source).trim().trim_start_matches(':').trim() == "number" - }); - if numeric && ident.is_some_and(|n| text(n, source) == id) { - return vec!["#".into()]; - } - } - } - vec!["*".into()] - } - "number" => vec![text(node, source).into()], - "member_expression" - if node.child_by_field_name("property").is_some_and(|n| { - let property = text(n, source); - property == "id" || property.ends_with("Id") || property.ends_with("_id") - }) => - { - vec!["#".into()] - } - "parenthesized_expression" | "as_expression" | "non_null_expression" => children(node) - .first() - .map_or(vec!["*".into()], |n| eval(*n)), - "call_expression" => { - let Ok(fun) = field(node, "function") else { - return vec!["*".into()]; - }; - let fun_name = name(fun, source); - if text(fun, source) == "URL.createObjectURL" { - return vec!["blob:*".into()]; - } - let args = node - .child_by_field_name("arguments") - .map(children) - .unwrap_or_default(); - if matches!(fun_name.as_str(), "encodeURIComponent" | "String") { - return vec!["*".into()]; - } - if fun_name == "withQuery" { - return args.first().map_or(vec!["*".into()], |n| eval(*n)); - } - let mut defs = vec![]; - descend(root, "function_declaration", &mut defs); - descend(root, "method_definition", &mut defs); - // Arrow-valued helpers such as `const base = (projectId) => ...`. - let mut vars = vec![]; - descend(root, "variable_declarator", &mut vars); - for var in vars { - if var - .child_by_field_name("name") - .is_some_and(|n| text(n, source) == fun_name) - && let Some(v) = var - .child_by_field_name("value") - .filter(|n| n.kind() == "arrow_function") - { - defs.push(v); - } - } - for def in defs { - if def.kind() != "arrow_function" - && !def - .child_by_field_name("name") - .is_some_and(|n| text(n, source) == fun_name) - { - continue; - } - let mut scope = bound.clone(); - if let Some(parameters) = def.child_by_field_name("parameters") { - for (i, p) in children(parameters).into_iter().enumerate() { - let id = p - .child_by_field_name("pattern") - .or_else(|| p.child_by_field_name("name")) - .unwrap_or(p); - let default = p.child_by_field_name("value"); - scope.insert( - text(id, source).into(), - args.get(i).map_or_else( - || default.map_or(vec!["*".into()], eval), - |n| eval(*n), - ), - ); - } - } - if let Some(body) = def.child_by_field_name("body") { - let mut returns = vec![]; - if body.kind() == "statement_block" { - descend(body, "return_statement", &mut returns); - } else { - returns.push(body); - } - let result: Vec<_> = returns - .into_iter() - .flat_map(|r| { - let v = if r.kind() == "return_statement" { - children(r).first().copied() - } else { - Some(r) - }; - v.map_or(vec![], |v| values(v, source, root, &scope, depth + 1)) - }) - .collect(); - if !result.is_empty() { - return result; - } - } - } - vec!["*".into()] - } - _ => vec!["*".into()], - } -} - -fn methods(init: Option>, source: &str) -> Result> { - let Some(init) = init else { - return Ok(vec!["get".into()]); - }; - if init.kind() == "call_expression" { - let fun = name(field(init, "function")?, source); - if fun == "write" { - let args = children(field(init, "arguments")?); - if let Some(n) = args.first().filter(|n| n.kind() == "string") { - return Ok(vec![ - text(*n, source)[1..text(*n, source).len() - 1].to_lowercase(), - ]); - } - } - bail!("unresolved request options: {}", text(init, source)); - } - if init.kind() != "object" { - bail!( - "request options need an explicit HTTP method: {}", - text(init, source) - ); - } - anyhow::ensure!( - !children(init).iter().any(|n| n.kind() == "spread_element"), - "request option spreads need explicit method evidence" - ); - for p in children(init).into_iter().filter(|n| n.kind() == "pair") { - if text(field(p, "key")?, source).trim_matches(['\'', '"']) == "method" { - let v = field(p, "value")?; - return match v.kind() { - "string" => Ok(vec![ - text(v, source)[1..text(v, source).len() - 1].to_lowercase(), - ]), - // Both branch methods are required; dynamic arbitrary methods fail. - "ternary_expression" => ["consequence", "alternative"] - .into_iter() - .map(|f| { - let n = field(v, f)?; - if n.kind() != "string" { - bail!("unresolved method") - }; - Ok(text(n, source)[1..text(n, source).len() - 1].to_lowercase()) - }) - .collect(), - _ => bail!("unresolved HTTP method: {}", text(v, source)), - }; - } - } - Ok(vec!["get".into()]) -} - -// Intersection of segment globs. '*' never crosses '/', so a collection call -// cannot accidentally require every nested resource beneath it. -pub fn intersects(a: &str, b: &str) -> bool { - fn go(a: &[u8], b: &[u8], i: usize, j: usize, seen: &mut HashSet<(usize, usize)>) -> bool { - if !seen.insert((i, j)) { - return false; - } - if i == a.len() && j == b.len() { - return true; - } - // '#' is a nonempty numeric identifier. It keeps a typed id lookup - // from accidentally selecting a reserved sibling such as /index_status. - match (a.get(i), b.get(j)) { - (Some(b'#'), Some(b'#')) => return go(a, b, i + 1, j + 1, seen), - (Some(b'#'), Some(b'*')) => { - return go(a, b, i + 1, j, seen) || go(a, b, i, j + 1, seen); - } - (Some(b'*'), Some(b'#')) => { - return go(a, b, i, j + 1, seen) || go(a, b, i + 1, j, seen); - } - (Some(b'#'), Some(d)) if d.is_ascii_digit() => { - return go(a, b, i + 1, j + 1, seen) || go(a, b, i, j + 1, seen); - } - (Some(d), Some(b'#')) if d.is_ascii_digit() => { - return go(a, b, i + 1, j + 1, seen) || go(a, b, i + 1, j, seen); - } - (Some(b'#'), _) | (_, Some(b'#')) => return false, - _ => (), - } - if a.get(i) == Some(&b'*') { - return go(a, b, i + 1, j, seen) - || (j < b.len() && b[j] != b'/' && go(a, b, i, j + 1, seen)); - } - if b.get(j) == Some(&b'*') { - return go(a, b, i, j + 1, seen) - || (i < a.len() && a[i] != b'/' && go(a, b, i + 1, j, seen)); - } - i < a.len() && j < b.len() && a[i] == b[j] && go(a, b, i + 1, j + 1, seen) - } - go(a.as_bytes(), b.as_bytes(), 0, 0, &mut HashSet::new()) -} -pub fn route_pattern(path: &str) -> String { - let mut out = String::new(); - let mut param = false; - for c in path.chars() { - if c == '{' { - param = true; - out.push('*'); - } else if c == '}' { - param = false; - } else if !param { - out.push(c); - } - } - out -} - -pub fn analyze( - source: &str, - tsx: bool, - declared: &BTreeMap, - transport: bool, -) -> Result { - analyze_file(source, tsx, declared, transport, None) -} - -fn navigation(node: Node<'_>, source: &str) -> bool { - let object = node.child_by_field_name("object").map(|n| text(n, source)); - match name(node, source).as_str() { - "assign" | "replace" | "href" => { - object.is_some_and(|s| s == "location" || s.ends_with(".location")) - } - "location" => object == Some("window"), - "open" => object == Some("window"), - _ => false, - } -} - -fn api_navigation(path: &str) -> bool { - path.starts_with("/api/") || path.starts_with("/oauth/") || path.starts_with("/.well-known/") -} - -fn analyze_file( - source: &str, - tsx: bool, - declared: &BTreeMap, - transport: bool, - source_path: Option<&str>, -) -> Result { - let mut parser = tree_sitter::Parser::new(); - parser.set_language(&if tsx { - tree_sitter_typescript::LANGUAGE_TSX.into() - } else { - tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into() - })?; - let tree = parser.parse(source, None).context("parse TypeScript")?; - let root = tree.root_node(); - if root.has_error() { - let mut nodes = vec![]; - descend(root, "ERROR", &mut nodes); - // The upstream TSX grammar treats a bare ampersand in JSX text as an - // error although TypeScript accepts it. No executable expression is - // inside such a text fragment; every other parse error fails closed. - let harmless = |n: &Node<'_>| { - tsx && n.parent().is_some_and(|p| p.kind() == "jsx_element") - && text(*n, source).starts_with('&') - && !text(*n, source).contains(['{', '}', '<', '>']) - }; - if nodes.is_empty() || !nodes.iter().all(harmless) { - bail!( - "TypeScript syntax errors prevent reliable coverage analysis: {:?}", - nodes - .iter() - .map(|n| (n.start_position().row + 1, text(*n, source))) - .collect::>() - ); - } - } - // Alternate networking implementations need an adapter instead of escaping - // the shared-transport checks with an unresolved dynamic URL. - let mut constructors = vec![]; - descend(root, "new_expression", &mut constructors); - for expression in constructors { - let constructor = name(field(expression, "constructor")?, source); - anyhow::ensure!( - !matches!( - constructor.as_str(), - "XMLHttpRequest" | "EventSource" | "WebSocket" - ), - "{constructor} needs an explicit contract transport adapter" - ); - } - let mut import_statements = vec![]; - descend(root, "import_statement", &mut import_statements); - for import in import_statements { - if text(import, source).starts_with("import type ") { - continue; - } - if let Some(module) = import.child_by_field_name("source") { - let module = text(module, source).trim_matches(['\'', '"']); - anyhow::ensure!( - !matches!( - module, - "axios" - | "ky" - | "got" - | "undici" - | "superagent" - | "http" - | "https" - | "node:http" - | "node:https" - ), - "network module {module} needs an explicit contract transport adapter" - ); - } - } - let mut calls = vec![]; - descend(root, "call_expression", &mut calls); - let mut result = Coverage { - operations: BTreeSet::new(), - call_sites: 0, - }; - let known = |name: &str| { - matches!( - name, - "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" - ) - }; - let mut aliases = BTreeMap::new(); - let mut imports = vec![]; - descend(root, "import_specifier", &mut imports); - for import in imports { - if let (Some(original), Some(alias)) = ( - import.child_by_field_name("name"), - import.child_by_field_name("alias"), - ) && known(text(original, source)) - { - aliases.insert( - text(alias, source).to_owned(), - text(original, source).to_owned(), - ); - } - } - let mut variables = vec![]; - descend(root, "variable_declarator", &mut variables); - for var in variables { - if let (Some(id), Some(value)) = ( - var.child_by_field_name("name"), - var.child_by_field_name("value"), - ) { - let original = name(value, source); - let original = aliases.get(&original).cloned().unwrap_or(original); - if known(&original) { - aliases.insert(text(id, source).to_owned(), original); - } - } - } - for call in calls { - let fun = field(call, "function")?; - let is_navigation = navigation(fun, source); - let fun_name = name(fun, source); - let fun_name = if is_navigation { - "fetch".into() - } else { - aliases.get(&fun_name).cloned().unwrap_or(fun_name) - }; - anyhow::ensure!( - fun_name != "sendBeacon", - "sendBeacon needs an explicit contract transport adapter" - ); - if !matches!( - fun_name.as_str(), - "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" - ) { - let args = call - .child_by_field_name("arguments") - .map(children) - .unwrap_or_default(); - let mut endpoints = vec![]; - for argument in &args { - endpoints.extend(values(*argument, source, root, &BTreeMap::new(), 0)); - if argument.kind() == "object" { - for pair in children(*argument) - .into_iter() - .filter(|n| n.kind() == "pair") - { - if pair - .child_by_field_name("key") - .is_some_and(|n| matches!(text(n, source), "url" | "path" | "uri")) - && let Some(value) = pair.child_by_field_name("value") - { - endpoints.extend(values(value, source, root, &BTreeMap::new(), 0)); - } - } - } - } - if endpoints.iter().any(|p| p.contains("/api/")) - && !matches!( - fun_name.as_str(), - "base" | "project" | "useCases" | "withQuery" - ) - { - bail!( - "unclassified use of an API URL by {fun_name} at line {}; add an explicit transport adapter", - call.start_position().row + 1 - ); - } - continue; - } - // Exactly the shared transports forward arbitrary paths. Their callers - // are checked here; adding another forwarding layer is not auto-excluded. - if transport && fun_name == "fetch" { - let owner = enclosing(call) - .child_by_field_name("name") - .map(|n| text(n, source)); - let args = children(field(call, "arguments")?); - let forwarded = args.first().is_some_and(|arg| { - let expression = text(*arg, source); - (expression == "path" && owner != Some("request")) - || (expression == "url" - && owner == Some("request") - && !values(*arg, source, root, &BTreeMap::new(), 0) - .iter() - .any(|path| path.contains("/api/"))) - }); - if forwarded - && matches!( - owner, - Some("request" | "fetchJson" | "fetchBlob" | "fetchDownload") - ) - { - continue; - } - } - if fun_name == "download" { - let mut defs = vec![]; - descend(root, "function_declaration", &mut defs); - let forwards = defs - .into_iter() - .filter(|d| { - d.child_by_field_name("name") - .is_some_and(|n| text(n, source) == "download") - }) - .any(|d| { - let mut inner = vec![]; - descend(d, "call_expression", &mut inner); - inner.into_iter().any(|c| { - c.child_by_field_name("function") - .is_some_and(|f| name(f, source) == "fetchDownload") - }) - }); - if !forwards { - continue; - } - } - let args = children(field(call, "arguments")?); - let Some(path) = args.first() else { - bail!("HTTP call has no path") - }; - let mut paths = values(*path, source, root, &BTreeMap::new(), 0); - if is_navigation { - // These existing flows navigate to user-selected UI paths or the - // external provider URL returned by the audited identity-link API. - // Neither is a new, statically chosen application HTTP operation. - let explicit_flow = (source_path == Some("frontend/src/pages/LoginPage.tsx") - && text(*path, source) == "returnTo") - || (source_path == Some("frontend/src/pages/AccountPage.tsx") - && text(*path, source).starts_with("await startIdentityLink(")); - if paths == ["*"] && explicit_flow { - continue; - } - if paths.iter().all(|p| p != "*" && !api_navigation(p)) { - continue; - } - } - // A forwarding download helper is covered at each concrete call site. - if fun_name == "fetchDownload" - && paths == ["*"] - && enclosing(call) - .child_by_field_name("name") - .is_some_and(|n| text(n, source) == "download") - { - continue; - } - paths = paths - .into_iter() - .map(|p| p.split('?').next().unwrap().to_owned()) - .collect(); - paths.sort(); - paths.dedup(); - let method = methods( - if fun_name == "download" || is_navigation { - None - } else { - args.get(1).copied() - }, - source, - ) - .with_context(|| format!("line {}", call.start_position().row + 1))?; - for path in paths { - if !api_navigation(&path) { - bail!( - "unresolved HTTP dependency at line {}: {}", - call.start_position().row + 1, - text(*args.first().unwrap(), source) - ); - } - for method in &method { - let found: Vec<_> = declared - .keys() - .filter(|op| { - op.method == *method && intersects(&path, &route_pattern(&op.path)) - }) - .cloned() - .collect(); - if found.is_empty() { - bail!( - "undocumented consumer operation: {} {} at line {}", - method, - path, - call.start_position().row + 1 - ); - } - result.operations.extend(found); - } - } - result.call_sites += 1; - } - // Browser navigation to fixed API paths is also a consumer dependency. - let mut attributes = vec![]; - descend(root, "jsx_attribute", &mut attributes); - for attribute in attributes { - let parts = children(attribute); - if parts.first().is_none_or(|n| text(*n, source) != "href") { - continue; - } - let Some(value) = parts.get(1) else { - continue; - }; - let value = if value.kind() == "jsx_expression" { - children(*value).first().copied().context("empty href")? - } else { - *value - }; - if source_path == Some("frontend/src/components/StatementText.tsx") - && text(value, source) == "n.href" - { - continue; - } - for path in values(value, source, root, &BTreeMap::new(), 0) { - let path = path.split('?').next().unwrap(); - if path == "*" { - bail!( - "unresolved browser navigation at line {}", - attribute.start_position().row + 1 - ); - } - if !api_navigation(path) { - continue; - } - let keys: Vec<_> = declared - .keys() - .filter(|key| key.method == "get" && intersects(path, &route_pattern(&key.path))) - .cloned() - .collect(); - if keys.is_empty() { - bail!("undocumented browser API dependency {path}"); - } - result.operations.extend(keys); - } - } - let mut assignments = vec![]; - descend(root, "assignment_expression", &mut assignments); - for assignment in assignments { - if !navigation(field(assignment, "left")?, source) { - continue; - } - for path in values( - field(assignment, "right")?, - source, - root, - &BTreeMap::new(), - 0, - ) { - let path = path.split('?').next().unwrap(); - anyhow::ensure!(path != "*", "unresolved browser navigation assignment"); - if api_navigation(path) { - let required: Vec<_> = declared - .keys() - .filter(|op| op.method == "get" && intersects(path, &route_pattern(&op.path))) - .cloned() - .collect(); - anyhow::ensure!( - !required.is_empty(), - "undocumented browser API dependency {path}" - ); - result.operations.extend(required); - } - } - } - Ok(result) -} - -pub fn scan(root: &Path, component: &str, spec: &Value) -> Result { - anyhow::ensure!( - matches!(component, "frontend" | "mcp"), - "consumer must be frontend or mcp" - ); - let directory = root.join(if component == "frontend" { - "frontend/src" - } else { - "mcp-server/src" - }); - let declared = operations(spec, None)?; - let mut result = Coverage { - operations: BTreeSet::new(), - call_sites: 0, - }; - for entry in walkdir::WalkDir::new(directory) { - let entry = entry?; - let path = entry.path(); - if !entry.file_type().is_file() - || !matches!( - path.extension().and_then(|x| x.to_str()), - Some("ts" | "tsx") - ) - { - continue; - } - let relative = path.strip_prefix(root)?.to_string_lossy(); - if relative.contains("/__tests__/") - || relative.ends_with(".test.ts") - || relative.ends_with("generated-contract.ts") - { - continue; - } - let source = std::fs::read_to_string(path)?; - let transport = - relative == "frontend/src/api/transport.ts" || relative == "mcp-server/src/client.ts"; - let coverage = analyze_file( - &source, - path.extension().is_some_and(|x| x == "tsx"), - &declared, - transport, - Some(relative.as_ref()), - ) - .with_context(|| relative.to_string())?; - result.call_sites += coverage.call_sites; - result.operations.extend(coverage.operations); - } - // Protocol navigation/discovery isn't fetch(): explicit, small, tested roots. - let protocol = if component == "frontend" { - vec!["auth_auth_external_start", "auth_auth_external_callback"] - } else { - vec![ - "oauth_authorization_server_metadata", - "oauth_protected_resource_metadata_mcp", - "oauth_register", - "oauth_authorize_page", - "oauth_authorize_decision", - "oauth_token", - ] - }; - for id in protocol { - let key = declared - .iter() - .find(|(_, v)| v["operationId"] == id) - .map(|(k, _)| k.clone()) - .with_context(|| format!("missing protocol operation {id}"))?; - result.operations.insert(key); - } - Ok(result) -} diff --git a/tools/marreq-contracts/src/coverage/analyzer.rs b/tools/marreq-contracts/src/coverage/analyzer.rs new file mode 100644 index 00000000..b7d3d117 --- /dev/null +++ b/tools/marreq-contracts/src/coverage/analyzer.rs @@ -0,0 +1,266 @@ +//! Transport recognition, navigation and fail-closed consumer call analysis. +use super::*; +use super::{routes::RouteIndex, syntax::*, transports}; +use anyhow::bail; +use tree_sitter::Node; + +fn navigation(node: Node<'_>, source: &str) -> bool { + let object = node.child_by_field_name("object").map(|n| text(n, source)); + match name(node, source).as_str() { + "assign" | "replace" | "href" => { + object.is_some_and(|s| s == "location" || s.ends_with(".location")) + } + "location" => object == Some("window"), + "open" => object == Some("window"), + _ => false, + } +} + +fn api_navigation(path: &str) -> bool { + path.starts_with("/api/") || path.starts_with("/oauth/") || path.starts_with("/.well-known/") +} + +pub(super) fn analyze_file( + source: &str, + tsx: bool, + declared: &BTreeMap, + transport: bool, + source_path: Option<&str>, +) -> Result { + let tree = parse(source, tsx)?; + let root = tree.root_node(); + let routes = RouteIndex::new(declared); + let mut calls = vec![]; + descend(root, "call_expression", &mut calls); + let mut result = Coverage { + operations: BTreeSet::new(), + call_sites: 0, + }; + let aliases = transports::aliases(root, source)?; + for call in calls { + let fun = field(call, "function")?; + let is_navigation = navigation(fun, source); + let fun_name = name(fun, source); + let fun_name = if is_navigation { + "fetch".into() + } else { + aliases.get(&fun_name).cloned().unwrap_or(fun_name) + }; + anyhow::ensure!( + fun_name != "sendBeacon", + "sendBeacon at {} needs an explicit contract transport adapter", + location(call) + ); + if !transports::known(&fun_name) { + let args = call + .child_by_field_name("arguments") + .map(children) + .unwrap_or_default(); + let mut endpoints = vec![]; + for argument in &args { + endpoints.extend(values(*argument, source, root, &BTreeMap::new(), 0)); + if argument.kind() == "object" { + for pair in children(*argument) + .into_iter() + .filter(|n| n.kind() == "pair") + { + if pair + .child_by_field_name("key") + .is_some_and(|n| matches!(text(n, source), "url" | "path" | "uri")) + && let Some(value) = pair.child_by_field_name("value") + { + endpoints.extend(values(value, source, root, &BTreeMap::new(), 0)); + } + } + } + } + if endpoints.iter().any(|p| p.contains("/api/")) + && !matches!( + fun_name.as_str(), + "base" | "project" | "useCases" | "withQuery" + ) + { + bail!( + "unclassified use of an API URL by {fun_name} at line {}; add an explicit transport adapter", + call.start_position().row + 1 + ); + } + continue; + } + // Exactly the shared transports forward arbitrary paths. Their callers + // are checked here; adding another forwarding layer is not auto-excluded. + if transport && fun_name == "fetch" { + let owner = enclosing(call) + .child_by_field_name("name") + .map(|n| text(n, source)); + let args = children(field(call, "arguments")?); + let forwarded = args.first().is_some_and(|arg| { + let expression = text(*arg, source); + (expression == "path" && owner != Some("request")) + || (expression == "url" + && owner == Some("request") + && !values(*arg, source, root, &BTreeMap::new(), 0) + .iter() + .any(|path| path.contains("/api/"))) + }); + if forwarded + && matches!( + owner, + Some("request" | "fetchJson" | "fetchBlob" | "fetchDownload") + ) + { + continue; + } + } + if fun_name == "download" { + let mut defs = vec![]; + descend(root, "function_declaration", &mut defs); + let forwards = defs + .into_iter() + .filter(|d| { + d.child_by_field_name("name") + .is_some_and(|n| text(n, source) == "download") + }) + .any(|d| { + let mut inner = vec![]; + descend(d, "call_expression", &mut inner); + inner.into_iter().any(|c| { + c.child_by_field_name("function") + .is_some_and(|f| name(f, source) == "fetchDownload") + }) + }); + if !forwards { + continue; + } + } + let args = children(field(call, "arguments")?); + let Some(path) = args.first() else { + bail!("HTTP call has no path") + }; + let mut paths = values(*path, source, root, &BTreeMap::new(), 0); + if is_navigation { + // These existing flows navigate to user-selected UI paths or the + // external provider URL returned by the audited identity-link API. + // Neither is a new, statically chosen application HTTP operation. + let explicit_flow = (source_path == Some("frontend/src/pages/LoginPage.tsx") + && text(*path, source) == "returnTo") + || (source_path == Some("frontend/src/pages/AccountPage.tsx") + && text(*path, source).starts_with("await startIdentityLink(")); + if paths == ["*"] && explicit_flow { + continue; + } + if paths.iter().all(|p| p != "*" && !api_navigation(p)) { + continue; + } + } + // A forwarding download helper is covered at each concrete call site. + if fun_name == "fetchDownload" + && paths == ["*"] + && enclosing(call) + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == "download") + { + continue; + } + paths = paths + .into_iter() + .map(|p| p.split('?').next().unwrap().to_owned()) + .collect(); + paths.sort(); + paths.dedup(); + let method = transports::methods( + if fun_name == "download" || is_navigation { + None + } else { + args.get(1).copied() + }, + source, + ) + .with_context(|| format!("line {}", call.start_position().row + 1))?; + for path in paths { + if !api_navigation(&path) { + bail!( + "unresolved HTTP dependency at line {}: {}", + call.start_position().row + 1, + text(*args.first().unwrap(), source) + ); + } + for method in &method { + result.operations.extend( + routes + .require(method, &path) + .with_context(|| format!("line {}", call.start_position().row + 1))?, + ); + } + } + result.call_sites += 1; + } + // Browser navigation to fixed API paths is also a consumer dependency. + let mut attributes = vec![]; + descend(root, "jsx_attribute", &mut attributes); + for attribute in attributes { + let parts = children(attribute); + if parts.first().is_none_or(|n| text(*n, source) != "href") { + continue; + } + let Some(value) = parts.get(1) else { + continue; + }; + let value = if value.kind() == "jsx_expression" { + children(*value).first().copied().context("empty href")? + } else { + *value + }; + if source_path == Some("frontend/src/components/StatementText.tsx") + && text(value, source) == "n.href" + { + continue; + } + for path in values(value, source, root, &BTreeMap::new(), 0) { + let path = path.split('?').next().unwrap(); + if path == "*" { + bail!( + "unresolved browser navigation at line {}", + attribute.start_position().row + 1 + ); + } + if !api_navigation(path) { + continue; + } + result.operations.extend( + routes.require("get", path).with_context(|| { + format!("href at line {}", attribute.start_position().row + 1) + })?, + ); + } + } + let mut assignments = vec![]; + descend(root, "assignment_expression", &mut assignments); + for assignment in assignments { + if !navigation(field(assignment, "left")?, source) { + continue; + } + for path in values( + field(assignment, "right")?, + source, + root, + &BTreeMap::new(), + 0, + ) { + let path = path.split('?').next().unwrap(); + anyhow::ensure!( + path != "*", + "unresolved browser navigation assignment at {}", + location(assignment) + ); + if api_navigation(path) { + result + .operations + .extend(routes.require("get", path).with_context(|| { + format!("navigation at line {}", assignment.start_position().row + 1) + })?); + } + } + } + Ok(result) +} diff --git a/tools/marreq-contracts/src/coverage/mod.rs b/tools/marreq-contracts/src/coverage/mod.rs new file mode 100644 index 00000000..25abdfa8 --- /dev/null +++ b/tools/marreq-contracts/src/coverage/mod.rs @@ -0,0 +1,118 @@ +//! Conservative TypeScript call analysis using the real TS/TSX grammar. +//! Unknown URL or HTTP-method expressions fail instead of dropping dependencies. +use crate::contract::{Operation, operations}; +use anyhow::{Context, Result}; +use serde::Serialize; +use serde_json::Value; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::Path, +}; + +#[derive(Debug, Serialize)] +pub struct Coverage { + pub operations: BTreeSet, + pub call_sites: usize, +} + +mod analyzer; +mod routes; +mod syntax; +mod transports; +pub use routes::{intersects, route_pattern}; + +/// Analyze one source text; `scan` supplies audited transport boundaries. +pub fn analyze( + source: &str, + tsx: bool, + declared: &BTreeMap, + transport: bool, +) -> Result { + analyzer::analyze_file(source, tsx, declared, transport, None) +} + +pub fn scan(root: &Path, component: &str, spec: &Value) -> Result { + anyhow::ensure!( + matches!(component, "frontend" | "mcp"), + "consumer must be frontend or mcp" + ); + let directory = root.join(if component == "frontend" { + "frontend/src" + } else { + "mcp-server/src" + }); + let declared = operations(spec, None)?; + let mut result = Coverage { + operations: BTreeSet::new(), + call_sites: 0, + }; + for entry in walkdir::WalkDir::new(directory) { + let entry = entry?; + let path = entry.path(); + if !entry.file_type().is_file() + || !matches!( + path.extension().and_then(|x| x.to_str()), + Some("ts" | "tsx") + ) + { + continue; + } + let relative = path.strip_prefix(root)?.to_string_lossy(); + if relative.contains("/__tests__/") + || relative.ends_with(".test.ts") + || relative.ends_with("generated-contract.ts") + { + continue; + } + let source = std::fs::read_to_string(path)?; + let transport = + relative == "frontend/src/api/transport.ts" || relative == "mcp-server/src/client.ts"; + let coverage = analyzer::analyze_file( + &source, + path.extension().is_some_and(|x| x == "tsx"), + &declared, + transport, + Some(relative.as_ref()), + ) + .with_context(|| relative.to_string())?; + result.call_sites += coverage.call_sites; + result.operations.extend(coverage.operations); + } + // Protocol navigation/discovery isn't fetch(): explicit, small, tested roots. + let protocol = if component == "frontend" { + vec!["auth_auth_external_start", "auth_auth_external_callback"] + } else { + vec![ + "oauth_authorization_server_metadata", + "oauth_protected_resource_metadata_mcp", + "oauth_register", + "oauth_authorize_page", + "oauth_authorize_decision", + "oauth_token", + ] + }; + for id in protocol { + let key = declared + .iter() + .find(|(_, v)| v["operationId"] == id) + .map(|(k, _)| k.clone()) + .with_context(|| format!("missing protocol operation {id}"))?; + result.operations.insert(key); + } + Ok(result) +} + +/// Verify every discovered dependency and report each consumer's coverage. +pub fn validate(root: &Path, spec: &Value) -> Result> { + let mut counts = serde_json::Map::new(); + for component in ["frontend", "mcp"] { + let coverage = scan(root, component, spec)?; + let selected = crate::contract::select(spec, &coverage.operations, None)?; + crate::contract::validate_wire_contract(&selected)?; + counts.insert( + component.into(), + serde_json::json!(coverage.operations.len()), + ); + } + Ok(counts) +} diff --git a/tools/marreq-contracts/src/coverage/routes.rs b/tools/marreq-contracts/src/coverage/routes.rs new file mode 100644 index 00000000..2c942e89 --- /dev/null +++ b/tools/marreq-contracts/src/coverage/routes.rs @@ -0,0 +1,88 @@ +//! Segment matching shared by discovery and real HTTP contract assertions. +use super::*; +use std::collections::HashSet; + +// Intersection of segment globs. '*' never crosses '/', so a collection call +// cannot accidentally require every nested resource beneath it. +pub fn intersects(a: &str, b: &str) -> bool { + fn go(a: &[u8], b: &[u8], i: usize, j: usize, seen: &mut HashSet<(usize, usize)>) -> bool { + if !seen.insert((i, j)) { + return false; + } + if i == a.len() && j == b.len() { + return true; + } + // '#' is a nonempty numeric identifier. It keeps a typed id lookup + // from accidentally selecting a reserved sibling such as /index_status. + match (a.get(i), b.get(j)) { + (Some(b'#'), Some(b'#')) => return go(a, b, i + 1, j + 1, seen), + (Some(b'#'), Some(b'*')) => { + return go(a, b, i + 1, j, seen) || go(a, b, i, j + 1, seen); + } + (Some(b'*'), Some(b'#')) => { + return go(a, b, i, j + 1, seen) || go(a, b, i + 1, j, seen); + } + (Some(b'#'), Some(d)) if d.is_ascii_digit() => { + return go(a, b, i + 1, j + 1, seen) || go(a, b, i, j + 1, seen); + } + (Some(d), Some(b'#')) if d.is_ascii_digit() => { + return go(a, b, i + 1, j + 1, seen) || go(a, b, i + 1, j, seen); + } + (Some(b'#'), _) | (_, Some(b'#')) => return false, + _ => (), + } + if a.get(i) == Some(&b'*') { + return go(a, b, i + 1, j, seen) + || (j < b.len() && b[j] != b'/' && go(a, b, i, j + 1, seen)); + } + if b.get(j) == Some(&b'*') { + return go(a, b, i, j + 1, seen) + || (i < a.len() && a[i] != b'/' && go(a, b, i + 1, j, seen)); + } + i < a.len() && j < b.len() && a[i] == b[j] && go(a, b, i + 1, j + 1, seen) + } + go(a.as_bytes(), b.as_bytes(), 0, 0, &mut HashSet::new()) +} +pub fn route_pattern(path: &str) -> String { + let mut out = String::new(); + let mut param = false; + for c in path.chars() { + if c == '{' { + param = true; + out.push('*'); + } else if c == '}' { + param = false; + } else if !param { + out.push(c); + } + } + out +} + +/// Compile path patterns once per source file, not once per HTTP call. +pub(super) struct RouteIndex<'a> { + patterns: Vec<(&'a Operation, String)>, +} +impl<'a> RouteIndex<'a> { + pub fn new(declared: &'a BTreeMap) -> Self { + Self { + patterns: declared + .keys() + .map(|op| (op, route_pattern(&op.path))) + .collect(), + } + } + pub fn require(&self, method: &str, path: &str) -> Result> { + let found: Vec<_> = self + .patterns + .iter() + .filter(|(op, pattern)| op.method == method && intersects(path, pattern)) + .map(|(op, _)| (*op).clone()) + .collect(); + anyhow::ensure!( + !found.is_empty(), + "undocumented consumer operation: {method} {path}" + ); + Ok(found) + } +} diff --git a/tools/marreq-contracts/src/coverage/syntax.rs b/tools/marreq-contracts/src/coverage/syntax.rs new file mode 100644 index 00000000..9cda432c --- /dev/null +++ b/tools/marreq-contracts/src/coverage/syntax.rs @@ -0,0 +1,303 @@ +//! TypeScript AST access and bounded, finite string-expression evaluation. +use anyhow::{Context, Result, bail}; +use std::collections::BTreeMap; +use tree_sitter::Node; + +pub(super) fn children(node: Node<'_>) -> Vec> { + let mut cursor = node.walk(); + node.named_children(&mut cursor).collect() +} +pub(super) fn descend<'a>(node: Node<'a>, kind: &str, found: &mut Vec>) { + if node.kind() == kind { + found.push(node); + } + for child in children(node) { + descend(child, kind, found); + } +} +pub(super) fn text<'a>(node: Node<'_>, source: &'a str) -> &'a str { + &source[node.byte_range()] +} +pub(super) fn field<'a>(node: Node<'a>, name: &str) -> Result> { + node.child_by_field_name(name).context("missing AST field") +} +pub(super) fn name(node: Node<'_>, source: &str) -> String { + if matches!( + node.kind(), + "await_expression" | "parenthesized_expression" | "instantiation_expression" + ) && let Some(inner) = children(node).first() + { + return name(*inner, source); + } + if node.kind() == "subscript_expression" + && let Some(index) = node + .child_by_field_name("index") + .filter(|n| n.kind() == "string") + { + return text(index, source).trim_matches(['\'', '"']).into(); + } + node.child_by_field_name("property") + .map_or_else(|| text(node, source), |n| text(n, source)) + .to_owned() +} +pub(super) fn enclosing(mut node: Node<'_>) -> Node<'_> { + while let Some(parent) = node.parent() { + if matches!( + node.kind(), + "function_declaration" | "method_definition" | "arrow_function" + ) { + return node; + } + node = parent; + } + node +} +fn combine(left: Vec, right: Vec) -> Vec { + left.into_iter() + .flat_map(|a| right.iter().map(move |b| format!("{a}{b}"))) + .collect() +} + +// Only finite string construction is evaluated. Unknown parameter values become +// a segment wildcard; an entirely unknown endpoint or HTTP method is rejected. +pub(super) fn values( + node: Node<'_>, + source: &str, + root: Node<'_>, + bound: &BTreeMap>, + depth: usize, +) -> Vec { + if depth > 16 { + return vec!["*".into()]; + } + let eval = |n| values(n, source, root, bound, depth + 1); + match node.kind() { + "string" => vec![text(node, source)[1..text(node, source).len() - 1].to_owned()], + "template_string" => { + let mut result = vec![String::new()]; + for child in children(node) { + let part = if child.kind() == "string_fragment" { + vec![text(child, source).to_owned()] + } else if child.kind() == "template_substitution" { + children(child) + .first() + .map_or(vec!["*".into()], |n| eval(*n)) + } else { + vec![text(child, source).to_owned()] + }; + result = combine(result, part); + if result.len() > 128 { + return vec!["*".into()]; + } + } + result + } + "ternary_expression" => ["consequence", "alternative"] + .into_iter() + .filter_map(|f| node.child_by_field_name(f)) + .flat_map(eval) + .collect(), + "binary_expression" + if node + .child_by_field_name("operator") + .is_some_and(|n| text(n, source) == "+") => + { + combine( + eval(field(node, "left").unwrap()), + eval(field(node, "right").unwrap()), + ) + } + "identifier" => { + let id = text(node, source); + if let Some(v) = bound.get(id) { + return v.clone(); + } + let scope = enclosing(node); + let mut vars = vec![]; + descend(scope, "variable_declarator", &mut vars); + if let Some(value) = vars + .into_iter() + .filter(|v| { + v.start_byte() < node.start_byte() + && v.parent() + .and_then(|p| p.child(0)) + .is_some_and(|n| n.kind() == "const") + }) + .filter(|v| { + let mut block = v.parent(); + while block.is_some_and(|p| !matches!(p.kind(), "statement_block" | "program")) + { + block = block.and_then(|p| p.parent()); + } + let mut parent = Some(node); + while let Some(p) = parent { + if Some(p) == block { + return true; + } + parent = p.parent(); + } + false + }) + .rev() + .find_map(|v| { + (v.child_by_field_name("name") + .is_some_and(|n| text(n, source) == id)) + .then(|| v.child_by_field_name("value")) + .flatten() + }) + { + return eval(value); + } + if let Some(parameters) = scope.child_by_field_name("parameters") { + for parameter in children(parameters) { + let ident = parameter + .child_by_field_name("pattern") + .or_else(|| parameter.child_by_field_name("name")); + let numeric = parameter.child_by_field_name("type").is_some_and(|t| { + text(t, source).trim().trim_start_matches(':').trim() == "number" + }); + if numeric && ident.is_some_and(|n| text(n, source) == id) { + return vec!["#".into()]; + } + } + } + vec!["*".into()] + } + "number" => vec![text(node, source).into()], + "member_expression" + if node.child_by_field_name("property").is_some_and(|n| { + let property = text(n, source); + property == "id" || property.ends_with("Id") || property.ends_with("_id") + }) => + { + vec!["#".into()] + } + "parenthesized_expression" | "as_expression" | "non_null_expression" => children(node) + .first() + .map_or(vec!["*".into()], |n| eval(*n)), + "call_expression" => { + let Ok(fun) = field(node, "function") else { + return vec!["*".into()]; + }; + let fun_name = name(fun, source); + if text(fun, source) == "URL.createObjectURL" { + return vec!["blob:*".into()]; + } + let args = node + .child_by_field_name("arguments") + .map(children) + .unwrap_or_default(); + if matches!(fun_name.as_str(), "encodeURIComponent" | "String") { + return vec!["*".into()]; + } + if fun_name == "withQuery" { + return args.first().map_or(vec!["*".into()], |n| eval(*n)); + } + let mut defs = vec![]; + descend(root, "function_declaration", &mut defs); + descend(root, "method_definition", &mut defs); + // Arrow-valued helpers such as `const base = (projectId) => ...`. + let mut vars = vec![]; + descend(root, "variable_declarator", &mut vars); + for var in vars { + if var + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == fun_name) + && let Some(v) = var + .child_by_field_name("value") + .filter(|n| n.kind() == "arrow_function") + { + defs.push(v); + } + } + for def in defs { + if def.kind() != "arrow_function" + && !def + .child_by_field_name("name") + .is_some_and(|n| text(n, source) == fun_name) + { + continue; + } + let mut scope = bound.clone(); + if let Some(parameters) = def.child_by_field_name("parameters") { + for (i, p) in children(parameters).into_iter().enumerate() { + let id = p + .child_by_field_name("pattern") + .or_else(|| p.child_by_field_name("name")) + .unwrap_or(p); + let default = p.child_by_field_name("value"); + scope.insert( + text(id, source).into(), + args.get(i).map_or_else( + || default.map_or(vec!["*".into()], eval), + |n| eval(*n), + ), + ); + } + } + if let Some(body) = def.child_by_field_name("body") { + let mut returns = vec![]; + if body.kind() == "statement_block" { + descend(body, "return_statement", &mut returns); + } else { + returns.push(body); + } + let result: Vec<_> = returns + .into_iter() + .flat_map(|r| { + let v = if r.kind() == "return_statement" { + children(r).first().copied() + } else { + Some(r) + }; + v.map_or(vec![], |v| values(v, source, root, &scope, depth + 1)) + }) + .collect(); + if !result.is_empty() { + return result; + } + } + } + vec!["*".into()] + } + _ => vec!["*".into()], + } +} + +pub(super) fn parse(source: &str, tsx: bool) -> Result { + let mut parser = tree_sitter::Parser::new(); + parser.set_language(&if tsx { + tree_sitter_typescript::LANGUAGE_TSX.into() + } else { + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into() + })?; + let tree = parser.parse(source, None).context("parse TypeScript")?; + let root = tree.root_node(); + if root.has_error() { + let mut nodes = vec![]; + descend(root, "ERROR", &mut nodes); + // The upstream TSX grammar treats a bare ampersand in JSX text as an + // error although TypeScript accepts it. No executable expression is + // inside such a text fragment; every other parse error fails closed. + let harmless = |n: &Node<'_>| { + tsx && n.parent().is_some_and(|p| p.kind() == "jsx_element") + && text(*n, source).starts_with('&') + && !text(*n, source).contains(['{', '}', '<', '>']) + }; + if nodes.is_empty() || !nodes.iter().all(harmless) { + bail!( + "TypeScript syntax errors prevent reliable coverage analysis: {:?}", + nodes + .iter() + .map(|n| (n.start_position().row + 1, text(*n, source))) + .collect::>() + ); + } + } + Ok(tree) +} + +pub(super) fn location(node: Node<'_>) -> String { + let p = node.start_position(); + format!("{}:{}", p.row + 1, p.column + 1) +} diff --git a/tools/marreq-contracts/src/coverage/transports.rs b/tools/marreq-contracts/src/coverage/transports.rs new file mode 100644 index 00000000..9b1bdfc4 --- /dev/null +++ b/tools/marreq-contracts/src/coverage/transports.rs @@ -0,0 +1,136 @@ +//! Recognized HTTP boundaries, aliases and explicitly unsupported transports. +use super::{syntax::*, *}; +use anyhow::bail; +use tree_sitter::Node; + +pub(super) fn known(name: &str) -> bool { + matches!( + name, + "fetch" | "fetchJson" | "fetchBlob" | "fetchDownload" | "request" | "download" + ) +} + +pub(super) fn methods(init: Option>, source: &str) -> Result> { + let Some(init) = init else { + return Ok(vec!["get".into()]); + }; + if init.kind() == "call_expression" { + let fun = name(field(init, "function")?, source); + if fun == "write" { + let args = children(field(init, "arguments")?); + if let Some(n) = args.first().filter(|n| n.kind() == "string") { + return Ok(vec![ + text(*n, source)[1..text(*n, source).len() - 1].to_lowercase(), + ]); + } + } + bail!("unresolved request options: {}", text(init, source)); + } + if init.kind() != "object" { + bail!( + "request options need an explicit HTTP method: {}", + text(init, source) + ); + } + anyhow::ensure!( + !children(init).iter().any(|n| n.kind() == "spread_element"), + "request option spreads need explicit method evidence" + ); + for p in children(init).into_iter().filter(|n| n.kind() == "pair") { + if text(field(p, "key")?, source).trim_matches(['\'', '"']) == "method" { + let v = field(p, "value")?; + return match v.kind() { + "string" => Ok(vec![ + text(v, source)[1..text(v, source).len() - 1].to_lowercase(), + ]), + // Both branch methods are required; dynamic arbitrary methods fail. + "ternary_expression" => ["consequence", "alternative"] + .into_iter() + .map(|f| { + let n = field(v, f)?; + if n.kind() != "string" { + bail!("unresolved method") + }; + Ok(text(n, source)[1..text(n, source).len() - 1].to_lowercase()) + }) + .collect(), + _ => bail!("unresolved HTTP method: {}", text(v, source)), + }; + } + } + Ok(vec!["get".into()]) +} + +pub(super) fn aliases(root: Node<'_>, source: &str) -> Result> { + // Alternate networking implementations need an adapter instead of escaping + // the shared-transport checks with an unresolved dynamic URL. + let mut constructors = vec![]; + descend(root, "new_expression", &mut constructors); + for expression in constructors { + let constructor = name(field(expression, "constructor")?, source); + anyhow::ensure!( + !matches!( + constructor.as_str(), + "XMLHttpRequest" | "EventSource" | "WebSocket" + ), + "{constructor} at {} needs an explicit contract transport adapter", + location(expression) + ); + } + let mut import_statements = vec![]; + descend(root, "import_statement", &mut import_statements); + for import in import_statements { + if text(import, source).starts_with("import type ") { + continue; + } + if let Some(module) = import.child_by_field_name("source") { + let module = text(module, source).trim_matches(['\'', '"']); + anyhow::ensure!( + !matches!( + module, + "axios" + | "ky" + | "got" + | "undici" + | "superagent" + | "http" + | "https" + | "node:http" + | "node:https" + ), + "network module {module} at {} needs an explicit contract transport adapter", + location(import) + ); + } + } + let mut aliases = BTreeMap::new(); + let mut imports = vec![]; + descend(root, "import_specifier", &mut imports); + for import in imports { + if let (Some(original), Some(alias)) = ( + import.child_by_field_name("name"), + import.child_by_field_name("alias"), + ) && known(text(original, source)) + { + aliases.insert( + text(alias, source).to_owned(), + text(original, source).to_owned(), + ); + } + } + let mut variables = vec![]; + descend(root, "variable_declarator", &mut variables); + for var in variables { + if let (Some(id), Some(value)) = ( + var.child_by_field_name("name"), + var.child_by_field_name("value"), + ) { + let original = name(value, source); + let original = aliases.get(&original).cloned().unwrap_or(original); + if known(&original) { + aliases.insert(text(id, source).to_owned(), original); + } + } + } + Ok(aliases) +} diff --git a/tools/marreq-contracts/src/main.rs b/tools/marreq-contracts/src/main.rs index cc1febc1..9bc0ef5a 100644 --- a/tools/marreq-contracts/src/main.rs +++ b/tools/marreq-contracts/src/main.rs @@ -149,28 +149,12 @@ fn run() -> anyhow::Result { } => { let spec = source::load(&spec)?; contract::validate(&spec)?; - for dir in ["frontend", "mcp-server"] { - let p: artifact::Policy = serde_json::from_value(contract::load( - &root.join(dir).join("compatibility.json"), - )?)?; - p.validate()?; - } - let range: artifact::FrontendRange = serde_json::from_value(contract::load( - &root.join("marreq-core/frontend_compatibility.json"), - )?)?; - anyhow::ensure!( - range.min_version.cmp_precedence(&range.max_version).is_le(), - "invalid backend frontend compatibility range" - ); - let mut counts = serde_json::Map::new(); - if !document_only { - for who in ["frontend", "mcp"] { - let c = coverage::scan(&root, who, &spec)?; - let selected = contract::select(&spec, &c.operations, None)?; - contract::validate_wire_contract(&selected)?; - counts.insert(who.into(), serde_json::json!(c.operations.len())); - } - } + artifact::validate_source_policies(&root)?; + let counts = if document_only { + serde_json::Map::new() + } else { + coverage::validate(&root, &spec)? + }; Ok(serde_json::json!({"valid":true,"consumer_operations":counts})) } Action::Compare { From 9e109e6474a8206fe06b9a594b74e0515f991741 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:40:19 +0100 Subject: [PATCH 13/18] test(contracts): organize suites and cover offline bundling and identity inputs --- tools/marreq-contracts/tests/compatibility.rs | 704 ------------------ tools/marreq-contracts/tests/contracts.rs | 17 + .../tests/contracts/artifacts.rs | 283 +++++++ .../tests/contracts/bundling.rs | 106 +++ tools/marreq-contracts/tests/contracts/cli.rs | 20 + .../tests/contracts/discovery.rs | 197 +++++ .../tests/contracts/fixtures.rs | 13 + .../tests/contracts/live_traffic.rs | 56 ++ .../tests/contracts/schema_compatibility.rs | 165 ++++ .../tests/contracts/security.rs | 53 ++ 10 files changed, 910 insertions(+), 704 deletions(-) delete mode 100644 tools/marreq-contracts/tests/compatibility.rs create mode 100644 tools/marreq-contracts/tests/contracts.rs create mode 100644 tools/marreq-contracts/tests/contracts/artifacts.rs create mode 100644 tools/marreq-contracts/tests/contracts/bundling.rs create mode 100644 tools/marreq-contracts/tests/contracts/cli.rs create mode 100644 tools/marreq-contracts/tests/contracts/discovery.rs create mode 100644 tools/marreq-contracts/tests/contracts/fixtures.rs create mode 100644 tools/marreq-contracts/tests/contracts/live_traffic.rs create mode 100644 tools/marreq-contracts/tests/contracts/schema_compatibility.rs create mode 100644 tools/marreq-contracts/tests/contracts/security.rs diff --git a/tools/marreq-contracts/tests/compatibility.rs b/tools/marreq-contracts/tests/compatibility.rs deleted file mode 100644 index 6b8eae44..00000000 --- a/tools/marreq-contracts/tests/compatibility.rs +++ /dev/null @@ -1,704 +0,0 @@ -use marreq_contracts::{ - artifact::{self, Artifact, Bundle, FrontendRange, Plan, Policy}, - contract::{self, Operation}, - coverage, -}; -use serde_json::{Value, json}; -use std::{ - collections::{BTreeMap, BTreeSet}, - path::Path, - process::Command, -}; -fn spec() -> Value { - json!({"openapi":"3.1.0","info":{"title":"fixture","version":"1.0.0"},"paths":{"/api/things":{"post":{"operationId":"create","security":[{"cookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"}},"required":["title"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"state":{"type":"string","enum":["draft","approved"]}},"required":["id","state"],"additionalProperties":true}}}}}}}},"components":{"securitySchemes":{"cookie":{"type":"apiKey","in":"cookie","name":"session"}}}}) -} -fn request(s: &mut Value) -> &mut Value { - &mut s["paths"]["/api/things"]["post"]["requestBody"]["content"]["application/json"]["schema"] -} -fn response(s: &mut Value) -> &mut Value { - &mut s["paths"]["/api/things"]["post"]["responses"]["200"]["content"]["application/json"]["schema"] -} -#[test] -fn valid_openapi() { - contract::validate(&spec()).unwrap(); -} -#[test] -fn missing_endpoint() { - let mut new = spec(); - new["paths"] = json!({}); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn required_response_removed() { - let mut new = spec(); - response(&mut new)["required"] = json!(["state"]); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn response_type_changed() { - let mut new = spec(); - response(&mut new)["properties"]["id"] = json!({"type":"string"}); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn new_required_request() { - let mut new = spec(); - request(&mut new)["properties"]["reason"] = json!({"type":"string"}); - request(&mut new)["required"] = json!(["title", "reason"]); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn additive_request_and_response() { - let mut new = spec(); - request(&mut new)["properties"]["optional"] = json!({"type":"integer"}); - response(&mut new)["properties"]["extra"] = json!({"type":"boolean"}); - contract::compare(&spec(), &new, "server").unwrap(); -} - -#[test] -fn multipart_required_field_changes_use_the_standards_engine() { - let mut old = spec(); - let schema = request(&mut old).clone(); - old["paths"]["/api/things"]["post"]["requestBody"]["content"] = - json!({"multipart/form-data":{"schema":schema}}); - let mut new = old.clone(); - let schema = &mut new["paths"]["/api/things"]["post"]["requestBody"]["content"]["multipart/form-data"] - ["schema"]; - schema["properties"]["new_field"] = json!({"type":"string"}); - schema["required"] = json!(["title", "new_field"]); - assert!(contract::compare(&old, &new, "server").is_err()); -} - -#[test] -fn file_download_media_type_removal_fails() { - let mut old = spec(); - old["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = - json!({"application/pdf":{"schema":{"type":"string","format":"binary"}}}); - let mut new = old.clone(); - new["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = - json!({"application/json":{"schema":{"type":"object"}}}); - assert!(contract::compare(&old, &new, "server").is_err()); -} -#[test] -fn response_enum_extension() { - let mut new = spec(); - response(&mut new)["properties"]["state"]["enum"] = json!(["draft", "approved", "reviewed"]); - assert!(contract::compare(&spec(), &new, "cloud").is_err()); -} -#[test] -fn request_enum_restriction() { - let mut old = spec(); - let mut new = spec(); - request(&mut old)["properties"]["title"]["enum"] = json!(["a", "b"]); - request(&mut new)["properties"]["title"]["enum"] = json!(["a"]); - assert!(contract::compare(&old, &new, "cloud").is_err()); -} -#[test] -fn authentication_requirement() { - let mut new = spec(); - new["components"]["securitySchemes"]["bearer"] = json!({"type":"http","scheme":"bearer"}); - new["paths"]["/api/things"]["post"]["security"] = json!([{"bearer":[]}]); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn unrelated_security_scheme_is_not_global_coupling() { - let mut new = spec(); - new["components"]["securitySchemes"]["unrelated"] = json!({"type":"http","scheme":"bearer"}); - contract::compare(&spec(), &new, "server").unwrap(); -} -#[test] -fn per_operation_scope_change() { - let mut new = spec(); - new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:write"]); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn unrelated_backend_endpoint_can_change() { - let mut old = spec(); - old["paths"]["/api/unrelated"] = json!({"get":{"responses":{"200":{"description":"old"}}}}); - let mut new = old.clone(); - new["paths"]["/api/unrelated"] = - json!({"delete":{"responses":{"204":{"description":"different"}}}}); - let required = BTreeSet::from([Operation { - method: "post".into(), - path: "/api/things".into(), - }]); - let consumer = contract::select(&old, &required, None).unwrap(); - contract::compare(&consumer, &new, "server").unwrap(); -} -#[test] -fn cloud_operation_not_required_on_server() { - let mut consumer = spec(); - consumer["paths"]["/api/register"] = - json!({"post":{"x-marreq-variants":["cloud"],"responses":{"200":{"description":"ok"}}}}); - contract::compare(&consumer, &spec(), "server").unwrap(); - assert!(contract::compare(&consumer, &spec(), "cloud").is_err()); -} -#[test] -fn required_unverified_operation_fails() { - let mut new = spec(); - new["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); - assert!(contract::compare(&spec(), &new, "server").is_err()); -} -#[test] -fn external_refs_fail_without_network() { - let mut new = spec(); - response(&mut new)["properties"]["id"] = json!({"$ref":"https://example.test/schema"}); - assert!( - contract::validate(&new) - .unwrap_err() - .to_string() - .contains("external references") - ); -} -#[test] -fn actual_json_instances_are_checked() { - let schema = response(&mut spec()).clone(); - contract::validate_instance(&spec(), &schema, &json!({"id":1,"state":"draft"})).unwrap(); - assert!(contract::validate_instance(&spec(), &schema, &json!({"state":"draft"})).is_err()); - assert!( - contract::validate_instance(&spec(), &schema, &json!({"id":"wrong","state":"draft"})) - .is_err() - ); -} -#[test] -fn invalid_policy() { - for value in [ - json!({}), - json!({"requires_backend_min":"1.x"}), - json!({"requires_backend_min":"2.0.0","requires_backend_max":"1.0.0"}), - json!({"requires_backend_min":"1.0.0","typo":"x"}), - ] { - let policy = serde_json::from_value::(value); - assert!(policy.is_err() || policy.unwrap().validate().is_err()); - } -} -fn plan( - dir: &Path, - backend_version: &str, - mcp_version: &str, - incompatible: bool, -) -> std::path::PathBuf { - let mut descriptors = BTreeMap::new(); - for (who, version) in [ - ("backend", backend_version), - ("frontend", "0.2.1"), - ("mcp", mcp_version), - ] { - let mut document = spec(); - if incompatible && who == "backend" { - response(&mut document)["required"] = json!(["state"]); - } - let bundle = Bundle { - format: "marreq.contract.v2".into(), - component: who.into(), - version: version.parse().unwrap(), - source_sha: "a".repeat(40), - variant: (who == "backend").then(|| "server".into()), - compatibility: (who != "backend").then(|| Policy { - requires_backend_min: "0.2.0".parse().unwrap(), - requires_backend_max: Some("0.2.99".parse().unwrap()), - }), - frontend_compatibility: (who == "backend").then(|| FrontendRange { - min_version: "0.2.0".parse().unwrap(), - max_version: "0.3.0".parse().unwrap(), - }), - contract: document, - }; - let filename = format!("{who}.json"); - contract::write(&dir.join(&filename), &bundle).unwrap(); - descriptors.insert( - who, - Artifact { - file: filename.into(), - sha256: artifact::hash(&std::fs::read(dir.join(format!("{who}.json"))).unwrap()), - version: bundle.version, - source_sha: bundle.source_sha, - }, - ); - } - let plan = Plan { - format: "marreq.deployment-contract.v2".into(), - variant: "server".into(), - backend: descriptors.remove("backend").unwrap(), - frontend: descriptors.remove("frontend").unwrap(), - mcp: descriptors.remove("mcp").unwrap(), - }; - let path = dir.join("plan.json"); - contract::write(&path, &plan).unwrap(); - path -} -#[test] -fn compatible_backend_only_and_mcp_only_updates() { - let dir = tempfile::tempdir().unwrap(); - for (b, m) in [("0.2.4", "0.3.0"), ("0.2.3", "0.3.1")] { - let path = plan(dir.path(), b, m, false); - assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); - } -} -#[test] -fn incompatible_backend_only_update() { - let dir = tempfile::tempdir().unwrap(); - assert!(artifact::verify(&plan(dir.path(), "0.2.4", "0.3.0", true), None).is_err()); -} - -#[test] -fn compatible_frontend_only_update() { - let dir = tempfile::tempdir().unwrap(); - let path = plan(dir.path(), "0.2.4", "0.3.0", false); - let bundle_path = dir.path().join("frontend.json"); - let mut bundle: Bundle = serde_json::from_value(contract::load(&bundle_path).unwrap()).unwrap(); - bundle.version = "0.2.2".parse().unwrap(); - contract::write(&bundle_path, &bundle).unwrap(); - let mut deployment: Plan = serde_json::from_value(contract::load(&path).unwrap()).unwrap(); - deployment.frontend.version = bundle.version; - deployment.frontend.sha256 = artifact::hash(&std::fs::read(bundle_path).unwrap()); - contract::write(&path, &deployment).unwrap(); - assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); -} - -#[test] -fn changed_authorization_guard_fails() { - let mut old = spec(); - old["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("project-member"); - let mut new = old.clone(); - new["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("instance-admin"); - assert!(contract::compare(&old, &new, "server").is_err()); -} - -#[test] -fn shared_transport_does_not_hide_concrete_calls() { - let ops = contract::operations(&spec(), None).unwrap(); - for source in [ - "function fetchJson(path:string){return fetch('/api/missing');}", - "class Client {request(path:string){const url='/api/missing';return fetch(url);}}", - ] { - assert!( - coverage::analyze(source, false, &ops, true).is_err(), - "{source}" - ); - } -} -#[test] -fn missing_historical_artifact() { - let dir = tempfile::tempdir().unwrap(); - let path = plan(dir.path(), "0.2.4", "0.3.0", false); - std::fs::remove_file(dir.path().join("mcp.json")).unwrap(); - assert!( - artifact::verify(&path, None) - .unwrap_err() - .to_string() - .contains("missing historical") - ); -} -#[test] -fn descriptor_source_and_version_mismatch() { - let dir = tempfile::tempdir().unwrap(); - for key in ["source_sha", "version"] { - let path = plan(dir.path(), "0.2.4", "0.3.0", false); - let mut p = contract::load(&path).unwrap(); - p["mcp"][key] = json!(if key == "source_sha" { - "b".repeat(40) - } else { - "0.3.9".into() - }); - contract::write(&path, &p).unwrap(); - assert!( - artifact::verify(&path, None) - .unwrap_err() - .to_string() - .contains("mismatch") - ); - } -} -#[test] -fn tampered_contract_hash() { - let dir = tempfile::tempdir().unwrap(); - let path = plan(dir.path(), "0.2.4", "0.3.0", false); - std::fs::write(dir.path().join("mcp.json"), "{}").unwrap(); - assert!( - artifact::verify(&path, None) - .unwrap_err() - .to_string() - .contains("SHA-256 mismatch") - ); -} - -#[test] -fn candidate_identity_requires_exact_source_version_and_bundle() { - let dir = tempfile::tempdir().unwrap(); - let path = plan(dir.path(), "0.2.4", "0.3.0", false); - let bundle = dir.path().join("backend.json"); - let version = "0.2.4".parse().unwrap(); - let wrong_version = "0.2.5".parse().unwrap(); - let source = "a".repeat(40); - artifact::verify(&path, Some(("backend", &version, &source, &bundle))).unwrap(); - for (version, source) in [(&wrong_version, source.clone()), (&version, "b".repeat(40))] { - assert!(artifact::verify(&path, Some(("backend", version, &source, &bundle))).is_err()); - } - let altered = dir.path().join("altered.json"); - let mut document = contract::load(&bundle).unwrap(); - document["contract"]["paths"] = json!({}); - contract::write(&altered, &document).unwrap(); - assert!(artifact::verify(&path, Some(("backend", &version, &source, &altered))).is_err()); -} -#[test] -fn cli_exit_codes_and_json_diagnostics() { - let dir = tempfile::tempdir().unwrap(); - for bad in [false, true] { - let path = plan(dir.path(), "0.2.4", "0.3.0", bad); - let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) - .arg("verify") - .arg("--manifest") - .arg(path) - .output() - .unwrap(); - assert_eq!(result.status.success(), !bad); - let body: Value = serde_json::from_slice(&result.stdout).unwrap(); - assert_eq!(body["compatible"], !bad); - } -} -#[test] -fn type_script_grammar_resolves_helpers_and_union_methods() { - let mut document = spec(); - document["paths"]["/api/projects/{project_id}/things/{id}"] = json!({"get":{"responses":{"200":{"description":"ok"}}},"delete":{"responses":{"204":{"description":"ok"}}}}); - let ops = contract::operations(&document, None).unwrap(); - let source = "const base = (id: number) => `/api/projects/${id}/things`; function read(id: number) { return fetchJson(`${base(1)}/${id}`, {method: remove ? 'DELETE' : 'GET'}); }"; - let coverage = coverage::analyze(source, false, &ops, false).unwrap(); - assert_eq!(coverage.operations.len(), 2); -} -#[test] -fn new_unmapped_call_cannot_bypass_coverage() { - let ops = contract::operations(&spec(), None).unwrap(); - assert!(coverage::analyze("fetch('/api/new-api')", false, &ops, false).is_err()); - assert!(coverage::analyze("fetch(url)", false, &ops, false).is_err()); -} -#[test] -fn dynamic_unverified_dependency_is_rejected() { - let mut doc = spec(); - doc["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); - let coverage = coverage::analyze( - "fetchJson('/api/things', {method:'POST'})", - false, - &contract::operations(&doc, None).unwrap(), - false, - ) - .unwrap(); - assert!(contract::select(&doc, &coverage.operations, None).is_err()); -} -#[test] -fn segment_globs_do_not_capture_unrelated_children() { - assert!(!coverage::intersects( - "/api/things/*", - "/api/things/*/versions" - )); - assert!(coverage::intersects("/api/things/*.pdf", "/api/things/*")); -} - -#[test] -fn await_generic_calls_are_dependencies() { - let coverage = coverage::analyze( - "async function f(){ return await fetchJson('/api/things',{method:'POST'}); }", - false, - &contract::operations(&spec(), None).unwrap(), - false, - ) - .unwrap(); - assert_eq!(coverage.call_sites, 1); - assert_eq!(coverage.operations.len(), 1); -} -#[test] -fn browser_navigation_is_a_dependency() { - let mut document = spec(); - document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); - let ops = contract::operations(&document, None).unwrap(); - assert_eq!( - coverage::analyze( - "const a=Reports & exports", - true, - &ops, - false - ) - .unwrap() - .operations - .len(), - 1 - ); - assert!( - coverage::analyze( - "const a=link", - true, - &ops, - false - ) - .is_err() - ); -} -#[test] -fn unsupported_schema_semantics_fail_locally() { - let mut new = spec(); - response(&mut new)["unevaluatedProperties"] = json!(false); - assert!( - contract::compare(&spec(), &new, "server") - .unwrap_err() - .to_string() - .contains("unevaluatedProperties") - ); -} -#[test] -fn fewer_required_scopes_are_compatible() { - let mut old = spec(); - old["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = - json!(["things:read", "things:write"]); - let mut new = old.clone(); - new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:read"]); - contract::compare(&old, &new, "server").unwrap(); -} - -#[test] -fn numeric_resource_ids_do_not_require_reserved_siblings() { - let mut document = spec(); - document["paths"]["/api/things/{id}"] = - json!({"get":{"responses":{"200":{"description":"ok"}}}}); - document["paths"]["/api/things/internal_stats"] = - json!({"get":{"responses":{"200":{"description":"unused"}}}}); - let coverage = coverage::analyze( - "function get(id:number){return fetchJson(`/api/things/${id}`);}", - false, - &contract::operations(&document, None).unwrap(), - false, - ) - .unwrap(); - assert_eq!(coverage.operations.len(), 1); - assert!(coverage::intersects("/api/things/#", "/api/things/123")); - assert!(!coverage::intersects( - "/api/things/#", - "/api/things/internal_stats" - )); -} - -#[test] -fn aliases_and_unrecognized_clients_cannot_hide_new_calls() { - let ops = contract::operations(&spec(), None).unwrap(); - for source in [ - "const f=fetch; f('/api/missing');", - "import {fetchJson as api} from './transport'; api('/api/missing');", - "axios.get('/api/missing');", - "axios({url:'/api/missing'});", - "client.open('POST', '/api/missing');", - "new XMLHttpRequest();", - "new EventSource(endpoint);", - "new WebSocket(endpoint);", - "navigator.sendBeacon(endpoint, data);", - "import client from 'axios'; client.get(endpoint);", - "import {request} from 'node:https';", - ] { - assert!( - coverage::analyze(source, false, &ops, false).is_err(), - "{source}" - ); - } - assert!(coverage::analyze("function f(url:string){if(true){const url='/api/things';}return fetchJson(url,{method:'POST'});}",false,&ops,false).is_err()); - assert!( - coverage::analyze( - "let path='/api/things';path=external;fetchJson(path,{method:'POST'});", - false, - &ops, - false - ) - .is_err() - ); -} - -#[test] -fn semver_build_metadata_does_not_change_policy_precedence() { - Policy { - requires_backend_min: "0.2.4".parse().unwrap(), - requires_backend_max: Some("0.2.4".parse().unwrap()), - } - .accepts(&"0.2.4+source.sha".parse().unwrap()) - .unwrap(); -} - -#[test] -fn unknown_schema_keywords_are_not_silently_accepted() { - let mut new = spec(); - response(&mut new)["customLimit"] = json!(1); - assert!( - contract::compare(&spec(), &new, "server") - .unwrap_err() - .to_string() - .contains("customLimit") - ); -} -#[test] -fn relative_plan_paths_work() { - let dir = tempfile::tempdir().unwrap(); - plan(dir.path(), "0.2.4", "0.3.0", false); - let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) - .current_dir(dir.path()) - .args(["verify", "--manifest", "plan.json"]) - .output() - .unwrap(); - assert!( - result.status.success(), - "{}", - String::from_utf8_lossy(&result.stdout) - ); -} - -#[test] -fn browser_calls_and_assignments_cannot_hide_dependencies() { - let mut document = spec(); - document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); - let operations = contract::operations(&document, None).unwrap(); - for source in [ - "window.location.assign('/api/missing')", - "window.open('/api/missing')", - "window.location.href='/api/missing'", - "window.location.href=unknown", - "window.location.assign(unknown)", - ] { - assert!( - coverage::analyze(source, false, &operations, false).is_err(), - "{source}" - ); - } - for source in [ - "window.location.assign('/api/things')", - "window.open('/api/things', '_blank')", - "window.location.href='/api/things'", - ] { - assert_eq!( - coverage::analyze(source, false, &operations, false) - .unwrap() - .operations - .len(), - 1, - "{source}" - ); - } - assert!( - coverage::analyze( - "const url=URL.createObjectURL(blob);window.location.href=url", - false, - &operations, - false - ) - .unwrap() - .operations - .is_empty() - ); -} - -#[test] -fn consumer_bundle_requires_committed_build_configuration() { - let dir = tempfile::tempdir().unwrap(); - let root = dir.path(); - std::fs::create_dir_all(root.join("docs/developer/openapi")).unwrap(); - std::fs::create_dir_all(root.join("frontend/src")).unwrap(); - contract::write( - &root.join(marreq_contracts::source::DEFAULT_SPEC), - &marreq_contracts::source::load( - &Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/developer/openapi/root.yaml"), - ).unwrap(), - ).unwrap(); - std::fs::write( - root.join("frontend/src/client.ts"), - "fetch('/api/meta/build');", - ) - .unwrap(); - std::fs::write( - root.join("frontend/compatibility.json"), - r#"{"requires_backend_min":"0.2.0","requires_backend_max":"0.2.99"}"#, - ) - .unwrap(); - std::fs::write(root.join("frontend/vite.config.ts"), "export default {};").unwrap(); - for args in [ - vec!["init", "--quiet"], - vec!["add", "."], - vec![ - "-c", - "user.name=Contract Test", - "-c", - "user.email=contract@example.test", - "commit", - "--quiet", - "-m", - "fixture", - ], - ] { - assert!( - Command::new("git") - .args(args) - .current_dir(root) - .status() - .unwrap() - .success() - ); - } - let version = "0.2.1".parse().unwrap(); - let clean = artifact::export(root, "frontend", version, None).unwrap(); - assert_eq!(clean.source_sha.len(), 40); - std::fs::write( - root.join("frontend/vite.config.ts"), - "export default { define: {api:'/changed'} };", - ) - .unwrap(); - let error = artifact::export(root, "frontend", "0.2.1".parse().unwrap(), None).unwrap_err(); - assert!(error.to_string().contains("commit source/contract inputs")); -} - -#[test] -fn traffic_requires_both_real_backend_variants() { - let mut document = spec(); - document["paths"] = json!({}); - let targets = [ - ("get", "/api/meta/build", 200), - ("get", "/api/meta/deployment", 200), - ("post", "/api/projects/{project_id}/use-cases", 201), - ( - "put", - "/api/projects/{project_id}/use-cases/{id}/draft", - 200, - ), - ("get", "/api/projects/{project_id}/use-cases", 403), - ]; - let mut records = vec![]; - for (method, path, status) in targets { - document["paths"][path][method] = json!({"responses":{status.to_string():{"description":"fixture","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"}}}}}}}}); - for variant in ["server", "cloud"] { - records.push(json!({"variant":variant,"method":method,"path":path.replace("{project_id}","1").replace("{id}","2"),"status":status,"response":{"id":1}})); - } - } - let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("traffic.jsonl"); - let write = |records: &[Value]| { - std::fs::write( - &path, - records - .iter() - .map(Value::to_string) - .collect::>() - .join("\n"), - ) - .unwrap() - }; - write(&records); - marreq_contracts::traffic::verify(&document, &path).unwrap(); - let server_only: Vec<_> = records - .iter() - .filter(|r| r["variant"] == "server") - .cloned() - .collect(); - write(&server_only); - assert!( - marreq_contracts::traffic::verify(&document, &path) - .unwrap_err() - .to_string() - .contains("cloud real-backend evidence missing") - ); - records[0]["response"]["id"] = json!("wrong type"); - write(&records); - assert!(marreq_contracts::traffic::verify(&document, &path).is_err()); -} diff --git a/tools/marreq-contracts/tests/contracts.rs b/tools/marreq-contracts/tests/contracts.rs new file mode 100644 index 00000000..2cd22324 --- /dev/null +++ b/tools/marreq-contracts/tests/contracts.rs @@ -0,0 +1,17 @@ +//! Focused suites share one small wire fixture and retain all negative cases. +#[path = "contracts/artifacts.rs"] +mod artifacts; +#[path = "contracts/bundling.rs"] +mod bundling; +#[path = "contracts/cli.rs"] +mod cli; +#[path = "contracts/discovery.rs"] +mod discovery; +#[path = "contracts/fixtures.rs"] +mod fixtures; +#[path = "contracts/live_traffic.rs"] +mod live_traffic; +#[path = "contracts/schema_compatibility.rs"] +mod schema_compatibility; +#[path = "contracts/security.rs"] +mod security; diff --git a/tools/marreq-contracts/tests/contracts/artifacts.rs b/tools/marreq-contracts/tests/contracts/artifacts.rs new file mode 100644 index 00000000..efb4cc3d --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/artifacts.rs @@ -0,0 +1,283 @@ +use super::fixtures::{response, spec}; +use marreq_contracts::{ + artifact::{self, Artifact, Bundle, FrontendRange, Plan, Policy}, + contract, +}; +use serde_json::json; +use std::{collections::BTreeMap, path::Path, process::Command}; + +#[test] +fn invalid_policy() { + for value in [ + json!({}), + json!({"requires_backend_min":"1.x"}), + json!({"requires_backend_min":"2.0.0","requires_backend_max":"1.0.0"}), + json!({"requires_backend_min":"1.0.0","typo":"x"}), + ] { + let policy = serde_json::from_value::(value); + assert!(policy.is_err() || policy.unwrap().validate().is_err()); + } +} + +pub(super) fn plan( + dir: &Path, + backend_version: &str, + mcp_version: &str, + incompatible: bool, +) -> std::path::PathBuf { + let mut descriptors = BTreeMap::new(); + for (who, version) in [ + ("backend", backend_version), + ("frontend", "0.2.1"), + ("mcp", mcp_version), + ] { + let mut document = spec(); + if incompatible && who == "backend" { + response(&mut document)["required"] = json!(["state"]); + } + let bundle = Bundle { + format: "marreq.contract.v2".into(), + component: who.into(), + version: version.parse().unwrap(), + source_sha: "a".repeat(40), + variant: (who == "backend").then(|| "server".into()), + compatibility: (who != "backend").then(|| Policy { + requires_backend_min: "0.2.0".parse().unwrap(), + requires_backend_max: Some("0.2.99".parse().unwrap()), + }), + frontend_compatibility: (who == "backend").then(|| FrontendRange { + min_version: "0.2.0".parse().unwrap(), + max_version: "0.3.0".parse().unwrap(), + }), + contract: document, + }; + let filename = format!("{who}.json"); + contract::write(&dir.join(&filename), &bundle).unwrap(); + descriptors.insert( + who, + Artifact { + file: filename.into(), + sha256: artifact::hash(&std::fs::read(dir.join(format!("{who}.json"))).unwrap()), + version: bundle.version, + source_sha: bundle.source_sha, + }, + ); + } + let plan = Plan { + format: "marreq.deployment-contract.v2".into(), + variant: "server".into(), + backend: descriptors.remove("backend").unwrap(), + frontend: descriptors.remove("frontend").unwrap(), + mcp: descriptors.remove("mcp").unwrap(), + }; + let path = dir.join("plan.json"); + contract::write(&path, &plan).unwrap(); + path +} + +#[test] +fn compatible_backend_only_and_mcp_only_updates() { + let dir = tempfile::tempdir().unwrap(); + for (b, m) in [("0.2.4", "0.3.0"), ("0.2.3", "0.3.1")] { + let path = plan(dir.path(), b, m, false); + assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); + } +} + +#[test] +fn incompatible_backend_only_update() { + let dir = tempfile::tempdir().unwrap(); + assert!(artifact::verify(&plan(dir.path(), "0.2.4", "0.3.0", true), None).is_err()); +} + +#[test] +fn compatible_frontend_only_update() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let bundle_path = dir.path().join("frontend.json"); + let mut bundle: Bundle = serde_json::from_value(contract::load(&bundle_path).unwrap()).unwrap(); + bundle.version = "0.2.2".parse().unwrap(); + contract::write(&bundle_path, &bundle).unwrap(); + let mut deployment: Plan = serde_json::from_value(contract::load(&path).unwrap()).unwrap(); + deployment.frontend.version = bundle.version; + deployment.frontend.sha256 = artifact::hash(&std::fs::read(bundle_path).unwrap()); + contract::write(&path, &deployment).unwrap(); + assert_eq!(artifact::verify(&path, None).unwrap()["compatible"], true); +} + +#[test] +fn missing_historical_artifact() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + std::fs::remove_file(dir.path().join("mcp.json")).unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("missing historical") + ); +} + +#[test] +fn descriptor_source_and_version_mismatch() { + let dir = tempfile::tempdir().unwrap(); + for key in ["source_sha", "version"] { + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let mut p = contract::load(&path).unwrap(); + p["mcp"][key] = json!(if key == "source_sha" { + "b".repeat(40) + } else { + "0.3.9".into() + }); + contract::write(&path, &p).unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("mismatch") + ); + } +} + +#[test] +fn tampered_contract_hash() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + std::fs::write(dir.path().join("mcp.json"), "{}").unwrap(); + assert!( + artifact::verify(&path, None) + .unwrap_err() + .to_string() + .contains("SHA-256 mismatch") + ); +} + +#[test] +fn candidate_identity_requires_exact_source_version_and_bundle() { + let dir = tempfile::tempdir().unwrap(); + let path = plan(dir.path(), "0.2.4", "0.3.0", false); + let bundle = dir.path().join("backend.json"); + let version = "0.2.4".parse().unwrap(); + let wrong_version = "0.2.5".parse().unwrap(); + let source = "a".repeat(40); + artifact::verify(&path, Some(("backend", &version, &source, &bundle))).unwrap(); + for (version, source) in [(&wrong_version, source.clone()), (&version, "b".repeat(40))] { + assert!(artifact::verify(&path, Some(("backend", version, &source, &bundle))).is_err()); + } + let altered = dir.path().join("altered.json"); + let mut document = contract::load(&bundle).unwrap(); + document["contract"]["paths"] = json!({}); + contract::write(&altered, &document).unwrap(); + assert!(artifact::verify(&path, Some(("backend", &version, &source, &altered))).is_err()); +} + +#[test] +fn semver_build_metadata_does_not_change_policy_precedence() { + Policy { + requires_backend_min: "0.2.4".parse().unwrap(), + requires_backend_max: Some("0.2.4".parse().unwrap()), + } + .accepts(&"0.2.4+source.sha".parse().unwrap()) + .unwrap(); +} + +#[test] +fn relative_plan_paths_work() { + let dir = tempfile::tempdir().unwrap(); + plan(dir.path(), "0.2.4", "0.3.0", false); + let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) + .current_dir(dir.path()) + .args(["verify", "--manifest", "plan.json"]) + .output() + .unwrap(); + assert!( + result.status.success(), + "{}", + String::from_utf8_lossy(&result.stdout) + ); +} + +#[test] +fn consumer_bundle_requires_committed_build_configuration() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path(); + std::fs::create_dir_all(root.join("docs/developer/openapi")).unwrap(); + std::fs::create_dir_all(root.join("frontend/src")).unwrap(); + contract::write( + &root.join(marreq_contracts::source::DEFAULT_SPEC), + &marreq_contracts::source::load( + &Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/developer/openapi/root.yaml"), + ) + .unwrap(), + ) + .unwrap(); + std::fs::write( + root.join("frontend/src/client.ts"), + "fetch('/api/meta/build');", + ) + .unwrap(); + std::fs::write( + root.join("frontend/compatibility.json"), + r#"{"requires_backend_min":"0.2.0","requires_backend_max":"0.2.99"}"#, + ) + .unwrap(); + std::fs::write(root.join("frontend/vite.config.ts"), "export default {};").unwrap(); + for args in [ + vec!["init", "--quiet"], + vec!["add", "."], + vec![ + "-c", + "user.name=Contract Test", + "-c", + "user.email=contract@example.test", + "commit", + "--quiet", + "-m", + "fixture", + ], + ] { + assert!( + Command::new("git") + .args(args) + .current_dir(root) + .status() + .unwrap() + .success() + ); + } + let version = "0.2.1".parse().unwrap(); + let clean = artifact::export(root, "frontend", version, None).unwrap(); + assert_eq!(clean.source_sha.len(), 40); + for relative in [ + "frontend/vite.config.ts", + "docs/developer/openapi/new-domain.yaml", + "scripts/bundle-openapi.mjs", + "package.json", + "package-lock.json", + ] { + let path = root.join(relative); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + let original = std::fs::read(&path).ok(); + std::fs::write(&path, "uncommitted input").unwrap(); + let error = artifact::export(root, "frontend", "0.2.1".parse().unwrap(), None).unwrap_err(); + assert!( + error.to_string().contains("commit source/contract inputs"), + "{relative}: {error:#}" + ); + if let Some(bytes) = original { + std::fs::write(path, bytes).unwrap(); + } else { + std::fs::remove_file(path).unwrap(); + } + } +} + +#[test] +fn inverted_backend_frontend_range_is_rejected() { + let range = FrontendRange { + min_version: "0.3.0".parse().unwrap(), + max_version: "0.2.0".parse().unwrap(), + }; + assert!(range.validate().is_err()); + assert!(range.accepts(&"0.2.1".parse().unwrap()).is_err()); +} diff --git a/tools/marreq-contracts/tests/contracts/bundling.rs b/tools/marreq-contracts/tests/contracts/bundling.rs new file mode 100644 index 00000000..2d8412d8 --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/bundling.rs @@ -0,0 +1,106 @@ +use marreq_contracts::{contract, source}; +use serde_json::json; +use std::{fs, path::Path}; + +fn fixture(root: &Path, reference: &str) -> std::path::PathBuf { + fs::write(root.join("root.yaml"), format!( + "openapi: 3.1.0\ninfo: {{title: fixture, version: 1.0.0}}\npaths:\n /api/example: {{$ref: '{reference}'}}\n" + )).unwrap(); + root.join("root.yaml") +} + +#[test] +fn deterministic_local_bundle_preserves_wire_and_security_metadata() { + let dir = tempfile::tempdir().unwrap(); + let path = fixture(dir.path(), "./paths.yaml#/example"); + fs::write( + dir.path().join("paths.yaml"), + r#" +example: + post: + operationId: example + security: [{bearer: [requirements:write]}] + x-marreq-variants: [cloud] + x-marreq-bearer-scopes: [requirements:write] + x-marreq-csrf: session-csrf + requestBody: + content: + application/json: + schema: {$ref: './schemas.yaml#/Request'} + responses: + '201': + description: Created + content: + application/json: + schema: {$ref: './schemas.yaml#/Response'} +"#, + ) + .unwrap(); + fs::write( + dir.path().join("schemas.yaml"), + r#" +Request: {type: object, properties: {name: {type: string}}, required: [name]} +Response: {type: object, properties: {id: {type: integer}}, required: [id]} +"#, + ) + .unwrap(); + let first = source::load(&path).unwrap(); + let second = source::load(&path).unwrap(); + assert_eq!( + serde_json::to_vec(&first).unwrap(), + serde_json::to_vec(&second).unwrap() + ); + let op = &first["paths"]["/api/example"]["post"]; + assert_eq!(op["security"], json!([{"bearer":["requirements:write"]}])); + assert_eq!(op["x-marreq-variants"], json!(["cloud"])); + assert_eq!(op["x-marreq-bearer-scopes"], json!(["requirements:write"])); + assert_eq!(op["x-marreq-csrf"], "session-csrf"); + let schema = &first["components"]["schemas"]["Request"]; + contract::validate_instance(&first, schema, &json!({"name":"accepted"})).unwrap(); + assert!(contract::validate_instance(&first, schema, &json!({})).is_err()); + assert_eq!( + first["components"]["schemas"]["Response"]["required"], + json!(["id"]) + ); +} + +#[test] +fn unresolved_references_fail_instead_of_producing_partial_contracts() { + for reference in ["./missing.yaml#/Thing", "./schemas.yaml#/Missing"] { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join("schemas.yaml"), "Thing: {type: string}\n").unwrap(); + assert!(source::load(&fixture(dir.path(), reference)).is_err()); + } +} + +#[test] +fn circular_reference_chains_are_rejected_by_the_standards_bundler() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join("a.yaml"), "$ref: './b.yaml'\n").unwrap(); + fs::write(dir.path().join("b.yaml"), "$ref: './a.yaml'\n").unwrap(); + let error = source::load(&fixture(dir.path(), "./a.yaml")).unwrap_err(); + assert!(error.to_string().contains("circular")); +} + +#[test] +fn network_and_directory_escape_references_are_rejected_offline() { + for reference in [ + "https://example.invalid/api.yaml", + "file:///etc/passwd", + "../outside.yaml", + ] { + let dir = tempfile::tempdir().unwrap(); + assert!(source::load(&fixture(dir.path(), reference)).is_err()); + } + #[cfg(unix)] + { + let parent = tempfile::tempdir().unwrap(); + let root = parent.path().join("contract"); + fs::create_dir(&root).unwrap(); + fs::write(parent.path().join("outside.yaml"), "get: {responses: {}}\n").unwrap(); + std::os::unix::fs::symlink(parent.path().join("outside.yaml"), root.join("escape.yaml")) + .unwrap(); + let error = source::load(&fixture(&root, "./escape.yaml")).unwrap_err(); + assert!(error.to_string().contains("escapes contract directory")); + } +} diff --git a/tools/marreq-contracts/tests/contracts/cli.rs b/tools/marreq-contracts/tests/contracts/cli.rs new file mode 100644 index 00000000..26464a76 --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/cli.rs @@ -0,0 +1,20 @@ +use super::artifacts::plan; +use serde_json::Value; +use std::process::Command; + +#[test] +fn cli_exit_codes_and_json_diagnostics() { + let dir = tempfile::tempdir().unwrap(); + for bad in [false, true] { + let path = plan(dir.path(), "0.2.4", "0.3.0", bad); + let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) + .arg("verify") + .arg("--manifest") + .arg(path) + .output() + .unwrap(); + assert_eq!(result.status.success(), !bad); + let body: Value = serde_json::from_slice(&result.stdout).unwrap(); + assert_eq!(body["compatible"], !bad); + } +} diff --git a/tools/marreq-contracts/tests/contracts/discovery.rs b/tools/marreq-contracts/tests/contracts/discovery.rs new file mode 100644 index 00000000..bcad9e28 --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/discovery.rs @@ -0,0 +1,197 @@ +use super::fixtures::spec; +use marreq_contracts::{contract, coverage}; +use serde_json::json; + +#[test] +fn shared_transport_does_not_hide_concrete_calls() { + let ops = contract::operations(&spec(), None).unwrap(); + for source in [ + "function fetchJson(path:string){return fetch('/api/missing');}", + "class Client {request(path:string){const url='/api/missing';return fetch(url);}}", + ] { + assert!( + coverage::analyze(source, false, &ops, true).is_err(), + "{source}" + ); + } +} + +#[test] +fn type_script_grammar_resolves_helpers_and_union_methods() { + let mut document = spec(); + document["paths"]["/api/projects/{project_id}/things/{id}"] = json!({"get":{"responses":{"200":{"description":"ok"}}},"delete":{"responses":{"204":{"description":"ok"}}}}); + let ops = contract::operations(&document, None).unwrap(); + let source = "const base = (id: number) => `/api/projects/${id}/things`; function read(id: number) { return fetchJson(`${base(1)}/${id}`, {method: remove ? 'DELETE' : 'GET'}); }"; + let coverage = coverage::analyze(source, false, &ops, false).unwrap(); + assert_eq!(coverage.operations.len(), 2); +} + +#[test] +fn new_unmapped_call_cannot_bypass_coverage() { + let ops = contract::operations(&spec(), None).unwrap(); + assert!(coverage::analyze("fetch('/api/new-api')", false, &ops, false).is_err()); + assert!(coverage::analyze("fetch(url)", false, &ops, false).is_err()); +} + +#[test] +fn dynamic_unverified_dependency_is_rejected() { + let mut doc = spec(); + doc["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); + let coverage = coverage::analyze( + "fetchJson('/api/things', {method:'POST'})", + false, + &contract::operations(&doc, None).unwrap(), + false, + ) + .unwrap(); + assert!(contract::select(&doc, &coverage.operations, None).is_err()); +} + +#[test] +fn segment_globs_do_not_capture_unrelated_children() { + assert!(!coverage::intersects( + "/api/things/*", + "/api/things/*/versions" + )); + assert!(coverage::intersects("/api/things/*.pdf", "/api/things/*")); +} + +#[test] +fn await_generic_calls_are_dependencies() { + let coverage = coverage::analyze( + "async function f(){ return await fetchJson('/api/things',{method:'POST'}); }", + false, + &contract::operations(&spec(), None).unwrap(), + false, + ) + .unwrap(); + assert_eq!(coverage.call_sites, 1); + assert_eq!(coverage.operations.len(), 1); +} + +#[test] +fn browser_navigation_is_a_dependency() { + let mut document = spec(); + document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); + let ops = contract::operations(&document, None).unwrap(); + assert_eq!( + coverage::analyze( + "const a=Reports & exports", + true, + &ops, + false + ) + .unwrap() + .operations + .len(), + 1 + ); + assert!( + coverage::analyze( + "const a=link", + true, + &ops, + false + ) + .is_err() + ); +} + +#[test] +fn numeric_resource_ids_do_not_require_reserved_siblings() { + let mut document = spec(); + document["paths"]["/api/things/{id}"] = + json!({"get":{"responses":{"200":{"description":"ok"}}}}); + document["paths"]["/api/things/internal_stats"] = + json!({"get":{"responses":{"200":{"description":"unused"}}}}); + let coverage = coverage::analyze( + "function get(id:number){return fetchJson(`/api/things/${id}`);}", + false, + &contract::operations(&document, None).unwrap(), + false, + ) + .unwrap(); + assert_eq!(coverage.operations.len(), 1); + assert!(coverage::intersects("/api/things/#", "/api/things/123")); + assert!(!coverage::intersects( + "/api/things/#", + "/api/things/internal_stats" + )); +} + +#[test] +fn aliases_and_unrecognized_clients_cannot_hide_new_calls() { + let ops = contract::operations(&spec(), None).unwrap(); + for source in [ + "const f=fetch; f('/api/missing');", + "import {fetchJson as api} from './transport'; api('/api/missing');", + "axios.get('/api/missing');", + "axios({url:'/api/missing'});", + "client.open('POST', '/api/missing');", + "new XMLHttpRequest();", + "new EventSource(endpoint);", + "new WebSocket(endpoint);", + "navigator.sendBeacon(endpoint, data);", + "import client from 'axios'; client.get(endpoint);", + "import {request} from 'node:https';", + ] { + assert!( + coverage::analyze(source, false, &ops, false).is_err(), + "{source}" + ); + } + assert!(coverage::analyze("function f(url:string){if(true){const url='/api/things';}return fetchJson(url,{method:'POST'});}",false,&ops,false).is_err()); + assert!( + coverage::analyze( + "let path='/api/things';path=external;fetchJson(path,{method:'POST'});", + false, + &ops, + false + ) + .is_err() + ); +} + +#[test] +fn browser_calls_and_assignments_cannot_hide_dependencies() { + let mut document = spec(); + document["paths"]["/api/things"]["get"] = json!({"responses":{"200":{"description":"ok"}}}); + let operations = contract::operations(&document, None).unwrap(); + for source in [ + "window.location.assign('/api/missing')", + "window.open('/api/missing')", + "window.location.href='/api/missing'", + "window.location.href=unknown", + "window.location.assign(unknown)", + ] { + assert!( + coverage::analyze(source, false, &operations, false).is_err(), + "{source}" + ); + } + for source in [ + "window.location.assign('/api/things')", + "window.open('/api/things', '_blank')", + "window.location.href='/api/things'", + ] { + assert_eq!( + coverage::analyze(source, false, &operations, false) + .unwrap() + .operations + .len(), + 1, + "{source}" + ); + } + assert!( + coverage::analyze( + "const url=URL.createObjectURL(blob);window.location.href=url", + false, + &operations, + false + ) + .unwrap() + .operations + .is_empty() + ); +} diff --git a/tools/marreq-contracts/tests/contracts/fixtures.rs b/tools/marreq-contracts/tests/contracts/fixtures.rs new file mode 100644 index 00000000..aa8558d0 --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/fixtures.rs @@ -0,0 +1,13 @@ +use serde_json::{Value, json}; + +pub(super) fn spec() -> Value { + json!({"openapi":"3.1.0","info":{"title":"fixture","version":"1.0.0"},"paths":{"/api/things":{"post":{"operationId":"create","security":[{"cookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"}},"required":["title"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"state":{"type":"string","enum":["draft","approved"]}},"required":["id","state"],"additionalProperties":true}}}}}}}},"components":{"securitySchemes":{"cookie":{"type":"apiKey","in":"cookie","name":"session"}}}}) +} + +pub(super) fn request(s: &mut Value) -> &mut Value { + &mut s["paths"]["/api/things"]["post"]["requestBody"]["content"]["application/json"]["schema"] +} + +pub(super) fn response(s: &mut Value) -> &mut Value { + &mut s["paths"]["/api/things"]["post"]["responses"]["200"]["content"]["application/json"]["schema"] +} diff --git a/tools/marreq-contracts/tests/contracts/live_traffic.rs b/tools/marreq-contracts/tests/contracts/live_traffic.rs new file mode 100644 index 00000000..9c24120c --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/live_traffic.rs @@ -0,0 +1,56 @@ +use super::fixtures::spec; +use serde_json::{Value, json}; + +#[test] +fn traffic_requires_both_real_backend_variants() { + let mut document = spec(); + document["paths"] = json!({}); + let targets = [ + ("get", "/api/meta/build", 200), + ("get", "/api/meta/deployment", 200), + ("post", "/api/projects/{project_id}/use-cases", 201), + ( + "put", + "/api/projects/{project_id}/use-cases/{id}/draft", + 200, + ), + ("get", "/api/projects/{project_id}/use-cases", 403), + ]; + let mut records = vec![]; + for (method, path, status) in targets { + document["paths"][path][method] = json!({"responses":{status.to_string():{"description":"fixture","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"}}}}}}}}); + for variant in ["server", "cloud"] { + records.push(json!({"variant":variant,"method":method,"path":path.replace("{project_id}","1").replace("{id}","2"),"status":status,"response":{"id":1}})); + } + } + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("traffic.jsonl"); + let write = |records: &[Value]| { + std::fs::write( + &path, + records + .iter() + .map(Value::to_string) + .collect::>() + .join("\n"), + ) + .unwrap() + }; + write(&records); + marreq_contracts::traffic::verify(&document, &path).unwrap(); + let server_only: Vec<_> = records + .iter() + .filter(|r| r["variant"] == "server") + .cloned() + .collect(); + write(&server_only); + assert!( + marreq_contracts::traffic::verify(&document, &path) + .unwrap_err() + .to_string() + .contains("cloud real-backend evidence missing") + ); + records[0]["response"]["id"] = json!("wrong type"); + write(&records); + assert!(marreq_contracts::traffic::verify(&document, &path).is_err()); +} diff --git a/tools/marreq-contracts/tests/contracts/schema_compatibility.rs b/tools/marreq-contracts/tests/contracts/schema_compatibility.rs new file mode 100644 index 00000000..46b28c9d --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/schema_compatibility.rs @@ -0,0 +1,165 @@ +use super::fixtures::{request, response, spec}; +use marreq_contracts::contract::{self, Operation}; +use serde_json::json; +use std::collections::BTreeSet; + +#[test] +fn valid_openapi() { + contract::validate(&spec()).unwrap(); +} + +#[test] +fn missing_endpoint() { + let mut new = spec(); + new["paths"] = json!({}); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn required_response_removed() { + let mut new = spec(); + response(&mut new)["required"] = json!(["state"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn response_type_changed() { + let mut new = spec(); + response(&mut new)["properties"]["id"] = json!({"type":"string"}); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn new_required_request() { + let mut new = spec(); + request(&mut new)["properties"]["reason"] = json!({"type":"string"}); + request(&mut new)["required"] = json!(["title", "reason"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn additive_request_and_response() { + let mut new = spec(); + request(&mut new)["properties"]["optional"] = json!({"type":"integer"}); + response(&mut new)["properties"]["extra"] = json!({"type":"boolean"}); + contract::compare(&spec(), &new, "server").unwrap(); +} + +#[test] +fn multipart_required_field_changes_use_the_standards_engine() { + let mut old = spec(); + let schema = request(&mut old).clone(); + old["paths"]["/api/things"]["post"]["requestBody"]["content"] = + json!({"multipart/form-data":{"schema":schema}}); + let mut new = old.clone(); + let schema = &mut new["paths"]["/api/things"]["post"]["requestBody"]["content"]["multipart/form-data"] + ["schema"]; + schema["properties"]["new_field"] = json!({"type":"string"}); + schema["required"] = json!(["title", "new_field"]); + assert!(contract::compare(&old, &new, "server").is_err()); +} + +#[test] +fn file_download_media_type_removal_fails() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = + json!({"application/pdf":{"schema":{"type":"string","format":"binary"}}}); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["responses"]["200"]["content"] = + json!({"application/json":{"schema":{"type":"object"}}}); + assert!(contract::compare(&old, &new, "server").is_err()); +} + +#[test] +fn response_enum_extension() { + let mut new = spec(); + response(&mut new)["properties"]["state"]["enum"] = json!(["draft", "approved", "reviewed"]); + assert!(contract::compare(&spec(), &new, "cloud").is_err()); +} + +#[test] +fn request_enum_restriction() { + let mut old = spec(); + let mut new = spec(); + request(&mut old)["properties"]["title"]["enum"] = json!(["a", "b"]); + request(&mut new)["properties"]["title"]["enum"] = json!(["a"]); + assert!(contract::compare(&old, &new, "cloud").is_err()); +} + +#[test] +fn unrelated_backend_endpoint_can_change() { + let mut old = spec(); + old["paths"]["/api/unrelated"] = json!({"get":{"responses":{"200":{"description":"old"}}}}); + let mut new = old.clone(); + new["paths"]["/api/unrelated"] = + json!({"delete":{"responses":{"204":{"description":"different"}}}}); + let required = BTreeSet::from([Operation { + method: "post".into(), + path: "/api/things".into(), + }]); + let consumer = contract::select(&old, &required, None).unwrap(); + contract::compare(&consumer, &new, "server").unwrap(); +} + +#[test] +fn cloud_operation_not_required_on_server() { + let mut consumer = spec(); + consumer["paths"]["/api/register"] = + json!({"post":{"x-marreq-variants":["cloud"],"responses":{"200":{"description":"ok"}}}}); + contract::compare(&consumer, &spec(), "server").unwrap(); + assert!(contract::compare(&consumer, &spec(), "cloud").is_err()); +} + +#[test] +fn required_unverified_operation_fails() { + let mut new = spec(); + new["paths"]["/api/things"]["post"]["x-marreq-unverified"] = json!(true); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn external_refs_fail_without_network() { + let mut new = spec(); + response(&mut new)["properties"]["id"] = json!({"$ref":"https://example.test/schema"}); + assert!( + contract::validate(&new) + .unwrap_err() + .to_string() + .contains("external references") + ); +} + +#[test] +fn actual_json_instances_are_checked() { + let schema = response(&mut spec()).clone(); + contract::validate_instance(&spec(), &schema, &json!({"id":1,"state":"draft"})).unwrap(); + assert!(contract::validate_instance(&spec(), &schema, &json!({"state":"draft"})).is_err()); + assert!( + contract::validate_instance(&spec(), &schema, &json!({"id":"wrong","state":"draft"})) + .is_err() + ); +} + +#[test] +fn unsupported_schema_semantics_fail_locally() { + let mut new = spec(); + response(&mut new)["unevaluatedProperties"] = json!(false); + assert!( + contract::compare(&spec(), &new, "server") + .unwrap_err() + .to_string() + .contains("unevaluatedProperties") + ); +} + +#[test] +fn unknown_schema_keywords_are_not_silently_accepted() { + let mut new = spec(); + response(&mut new)["customLimit"] = json!(1); + assert!( + contract::compare(&spec(), &new, "server") + .unwrap_err() + .to_string() + .contains("customLimit") + ); +} diff --git a/tools/marreq-contracts/tests/contracts/security.rs b/tools/marreq-contracts/tests/contracts/security.rs new file mode 100644 index 00000000..075726ae --- /dev/null +++ b/tools/marreq-contracts/tests/contracts/security.rs @@ -0,0 +1,53 @@ +use super::fixtures::spec; +use marreq_contracts::contract; +use serde_json::json; + +#[test] +fn authentication_requirement() { + let mut new = spec(); + new["components"]["securitySchemes"]["bearer"] = json!({"type":"http","scheme":"bearer"}); + new["paths"]["/api/things"]["post"]["security"] = json!([{"bearer":[]}]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn unrelated_security_scheme_is_not_global_coupling() { + let mut new = spec(); + new["components"]["securitySchemes"]["unrelated"] = json!({"type":"http","scheme":"bearer"}); + contract::compare(&spec(), &new, "server").unwrap(); +} + +#[test] +fn per_operation_scope_change() { + let mut new = spec(); + new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:write"]); + assert!(contract::compare(&spec(), &new, "server").is_err()); +} + +#[test] +fn changed_authorization_guard_fails() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("project-member"); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["x-marreq-authorization"] = json!("instance-admin"); + assert!(contract::compare(&old, &new, "server").is_err()); +} + +#[test] +fn fewer_required_scopes_are_compatible() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = + json!(["things:read", "things:write"]); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["x-marreq-bearer-scopes"] = json!(["things:read"]); + contract::compare(&old, &new, "server").unwrap(); +} + +#[test] +fn changing_csrf_requirements_is_incompatible() { + let mut old = spec(); + old["paths"]["/api/things"]["post"]["x-marreq-csrf"] = json!("session-csrf"); + let mut new = old.clone(); + new["paths"]["/api/things"]["post"]["x-marreq-csrf"] = json!("auth-origin-fallback"); + assert!(contract::compare(&old, &new, "server").is_err()); +} From 6ed1df1c40041386200de3af1c66be126f0e4428 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:42:00 +0100 Subject: [PATCH 14/18] test(contracts): make shared wire fixture readable --- .../tests/contracts/fixtures.rs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/tools/marreq-contracts/tests/contracts/fixtures.rs b/tools/marreq-contracts/tests/contracts/fixtures.rs index aa8558d0..5e96e0a2 100644 --- a/tools/marreq-contracts/tests/contracts/fixtures.rs +++ b/tools/marreq-contracts/tests/contracts/fixtures.rs @@ -1,7 +1,82 @@ use serde_json::{Value, json}; pub(super) fn spec() -> Value { - json!({"openapi":"3.1.0","info":{"title":"fixture","version":"1.0.0"},"paths":{"/api/things":{"post":{"operationId":"create","security":[{"cookie":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"}},"required":["title"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"state":{"type":"string","enum":["draft","approved"]}},"required":["id","state"],"additionalProperties":true}}}}}}}},"components":{"securitySchemes":{"cookie":{"type":"apiKey","in":"cookie","name":"session"}}}}) + json!({ + "openapi": "3.1.0", + "info": { + "title": "fixture", + "version": "1.0.0" + }, + "paths": { + "/api/things": { + "post": { + "operationId": "create", + "security": [ + { + "cookie": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + } + }, + "required": [ + "title" + ], + "additionalProperties": false + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "state": { + "type": "string", + "enum": [ + "draft", + "approved" + ] + } + }, + "required": [ + "id", + "state" + ], + "additionalProperties": true + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "cookie": { + "type": "apiKey", + "in": "cookie", + "name": "session" + } + } + } + }) } pub(super) fn request(s: &mut Value) -> &mut Value { From adadf7849e148f52d43533ec546873ee9ff41c9e Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:42:00 +0100 Subject: [PATCH 15/18] docs(contracts): describe domain editing, tooling boundaries and image checks --- README.md | 2 +- docs/README.md | 2 +- docs/developer/http-api-contract.md | 51 +++++++++++++++++++++++++---- docs/developer/use-cases-api.md | 2 +- docs/releases.md | 9 +++++ 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index aa2e9e09..97c67b82 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Use the SPA (**Docker** `http://localhost:8080` or **`npm run dev`** in `fronten ## ๐Ÿ”Œ API Reference -**Interchangeable clients:** session auth, CSRF, and Docker/nginx notes are documented in [docs/developer/http-api-contract.md](docs/developer/http-api-contract.md). A partial [OpenAPI spec](docs/developer/openapi.yaml) covers auth and session-scoped project listing. +**Interchangeable clients:** session auth, CSRF, and Docker/nginx notes are documented in [docs/developer/http-api-contract.md](docs/developer/http-api-contract.md). The modular [OpenAPI contract](docs/developer/openapi/root.yaml) covers all frontend/MCP HTTP dependencies. ### Base URL ``` diff --git a/docs/README.md b/docs/README.md index 38e12b83..86fe0433 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,7 @@ This repoโ€™s documentation is organized by audience: - [Setup guide (`marreq-server` / `marreq-cloud`, Docker / local)](developer/setup.md) - [Releases & dual artifact versioning](releases.md) - [Workspace layout & deployment modes](developer/workspace-layout.md) -- [HTTP API contract (SPA / interchangeable clients)](developer/http-api-contract.md) โ€” auth, CSRF, cookies; partial [OpenAPI](developer/openapi.yaml) +- [HTTP API contract (SPA / interchangeable clients)](developer/http-api-contract.md) โ€” auth, CSRF, cookies; modular [OpenAPI](developer/openapi/root.yaml) - [Database setup](developer/database-setup.md) - [MCP setup](developer/mcp-setup.md) - [Semantic search](developer/semantic-search.md) diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index cbe51353..083fb6c6 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -209,7 +209,7 @@ Instance-wide. All routes require a **global administrator** (`users.is_admin`); Rocket mounts all JSON routes under `/api` in `marreq-core/src/api/mod.rs` (shared routes) and the deployment crate's `src/routes.rs` (deployment-specific routes). The project [README](../../README.md) includes a human-maintained endpoint summary (requirements, tests, matrix, baselines, MCP audit, etc.). -A minimal **OpenAPI 3** sketch for auth and session project listing lives in [`openapi.yaml`](openapi.yaml) (extend or replace with generated spec later). +The consumer-facing OpenAPI contract starts at [`openapi/root.yaml`](openapi/root.yaml); domain modules document its requests, responses and security requirements. ## Server flags (backend) @@ -229,7 +229,7 @@ See [`../../docker/README.md`](../../docker/README.md). ## Consumer-aware contract verification (#423 / #424) -`docs/developer/openapi.yaml` is the single wire contract. Only operations used by +`docs/developer/openapi/root.yaml` and its domain files are the authoritative wire contract. Only operations used by frontend or MCP belong in release compatibility checks. A route inventory is not serialization evidence: `x-marreq-unverified` means the operation still needs an audit and cannot be used in a releasable consumer bundle. Unconsumed backend @@ -245,7 +245,37 @@ or undocumented operations still fail closed. ### Architecture and rationale -- `tools/marreq-contracts` is a workspace Rust CLI/library. Serde/serde_yaml_ng parse +The root indexes standard `$ref` path items and components. Edit `paths/.yaml` +and `schemas/.yaml`; shared security and error responses live in `components/`. +Requirements history, project settings, notifications and use-case links have their +own modules, matching the backend responsibilities. No bundled snapshot is committed. + +`source::load` invokes **Redocly openapi-core 1.34.22**, already used by the type +pipeline and now an exact direct npm dependency with lockfile integrity. The short +`scripts/bundle-openapi.mjs` adapter forbids network/URI references and files outside +the root directory (including symlinks). Redocly resolves the references; errors or +remaining external references fail. Comparison and retained-bundle verification +read self-contained JSON and do not need Node or editable source files. + +For a tool that needs one document, generate an ignored, disposable copy: + +```sh +node scripts/bundle-openapi.mjs > docs/developer/openapi.yaml +``` + +Bundling tests check byte determinism, schemas and security metadata, missing +references, circular reference chains, network references and directory escapes. +The modularization was checked against the previous parsed document for exact +semantic equality, including all Marreq extensions. Type declarations are unchanged. + +- `tools/marreq-contracts` is a workspace Rust CLI/library. `source` handles + editable-document loading; `contract` owns selection, standards validation and + comparison; `artifact` owns identity/policies; `traffic` checks live evidence. + `coverage` separates the repository scan, AST/expression handling, transport + recognition, route matching and call analysis. Path patterns are prepared once + per file and shared by HTTP/navigation checks; the CLI delegates domain work. + Focused test modules share a single small wire fixture. + Serde/serde_yaml_ng parse data, `semver` validates versions, `sha2` pins bundle content, and `jsonschema` validates actual request/response bodies offline. - OpenAPI validation and breaking-change analysis use checksum-pinned **oasdiff @@ -284,7 +314,7 @@ existing requirements-compliance Python script is retained. ### Developer commands -Workspace tests, including coverage, require the pinned upstream engine. Install +Workspace tests, including coverage, require Node 22+, root `npm ci`, and the pinned upstream engine. Install it once and expose its destination on PATH (or set `OASDIFF` to that binary). Both contract CI and workspace coverage CI use the same installer. The installer currently handles Linux x86_64; other platforms may install the same @@ -293,7 +323,7 @@ upstream version themselves. No contract command downloads refs or schemas. ```sh scripts/install-oasdiff.sh export PATH="$HOME/.cache/marreq-contracts:$PATH" -npm ci # pinned generator and TypeScript compiler +npm ci # pinned offline bundler, type generator and TypeScript compiler cargo test --locked -p marreq-contracts cargo run --locked -p marreq-contracts -- validate cargo run --locked -p marreq-contracts -- coverage --component frontend @@ -311,7 +341,7 @@ without certifying consumer coverage; it cannot create a consumer release bundle no development bypass for an unverified consumer operation. When adding/changing an HTTP call, update the actual Rocket implementation and -OpenAPI, then regenerate declarations and extend existing consumer/producer tests. +the relevant OpenAPI domain module (and its root index for a new path/schema), then regenerate declarations and extend existing consumer/producer tests. A new unknown call, unresolved dynamic call, or newly used unverified operation fails validation. Remove `x-marreq-unverified` only after auditing request defaults, response nullability/required fields, errors/statuses, media, guards and scopes. @@ -343,3 +373,12 @@ The advisory policy is inherited unchanged from `main`, including its The newly reported `calamine` advisory RUSTSEC-2026-0436 is fixed with the targeted 0.38.0 update; no additional ignores are introduced. MCP production audit still reports a moderate advisory below the existing high-severity failure threshold. + +Production image regression checks build both `MARREQ_BIN=marreq-server` and +`MARREQ_BIN=marreq-cloud` through `docker/Dockerfile`. The cache layer copies only +workspace manifests plus stubs, including `tools/marreq-contracts`; real sources +arrive later. Variant/version/SHA arguments apply after dependency caching. The +runtime copies only the selected backend, migrations and existing operational +scripts/tools; contract analysis is a dev dependency and its source, Node and +standards binaries do not enter either backend image. Frontend and MCP images +build from their own inputs and committed declarations. diff --git a/docs/developer/use-cases-api.md b/docs/developer/use-cases-api.md index 3920ac80..1ba42564 100644 --- a/docs/developer/use-cases-api.md +++ b/docs/developer/use-cases-api.md @@ -169,7 +169,7 @@ Comments are immutable. `@username` mentions of project members send a `mentione ## Executable contract checks -Use-case request/response definitions are in `openapi.yaml`; consumer declarations +Use-case request/response definitions are in `openapi/root.yaml`; consumer declarations are generated with standard `openapi-typescript` through `node scripts/generate-api-types.mjs`. Native Rust tests and `scripts/test-api-contracts.sh` check representative real read/write, session, diff --git a/docs/releases.md b/docs/releases.md index 24ed5c07..0ae0c1aa 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -164,3 +164,12 @@ or production deployment is performed here. A document-only check is not a release-readiness assertion. Publication also requires passing consumer/handler tests and a provisioned immutable plan with genuine historical bundles. Missing release evidence must continue to fail closed. + +Editable contract export requires root `npm ci` for the lockfile-pinned offline +Redocly bundler. All `docs/developer/openapi/**` modules, the bundler script and +root npm manifests/lockfile must be committed alongside the Rust tool and component +inputs before export. The disposable `openapi.yaml` output is ignored and never +substitutes for these inputs. Retained bundle verification uses self-contained +JSON with the pinned standards engine; it never resolves editable source refs. +Frontend/MCP tags do not compile backend route tests in the release gate; backend +tags verify producer routes/serializers, and PR CI checks all production images. From 0641743f7dbc24f95760305bb2d55a66f1f8058c Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:49:03 +0100 Subject: [PATCH 16/18] fix(builds): isolate Docker inputs and bind them to source evidence --- .dockerignore | 11 +++++++++++ docs/releases.md | 2 +- tools/marreq-contracts/src/artifact.rs | 16 +++++++++++++--- .../tests/contracts/artifacts.rs | 2 ++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8b7d9fe5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Each image installs/builds its own lockfile-pinned dependencies. +.git +**/node_modules +**/target +**/dist +/coverage +/frontend/coverage +/mcp-server/coverage +coverage_html +.env +.env.* diff --git a/docs/releases.md b/docs/releases.md index 0ae0c1aa..f0b1e397 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -167,7 +167,7 @@ genuine historical bundles. Missing release evidence must continue to fail close Editable contract export requires root `npm ci` for the lockfile-pinned offline Redocly bundler. All `docs/developer/openapi/**` modules, the bundler script and -root npm manifests/lockfile must be committed alongside the Rust tool and component +root npm manifests/lockfile and Docker build/proxy inputs must be committed alongside the Rust tool and component inputs before export. The disposable `openapi.yaml` output is ignored and never substitutes for these inputs. Retained bundle verification uses self-contained JSON with the pinned standards engine; it never resolves editable source refs. diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs index c6cda30d..8abceb6a 100644 --- a/tools/marreq-contracts/src/artifact.rs +++ b/tools/marreq-contracts/src/artifact.rs @@ -204,20 +204,30 @@ pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> "tools/marreq-contracts", "Cargo.toml", "Cargo.lock", + ".dockerignore", ] .into_iter() .map(String::from) .collect(); if who == "backend" { inputs.extend( - ["marreq-core", "marreq-cloud", "marreq-server"] - .into_iter() - .map(String::from), + [ + "marreq-core", + "marreq-cloud", + "marreq-server", + "docker/Dockerfile", + "docker/docker-entrypoint.sh", + ] + .into_iter() + .map(String::from), ); } else { // Build configuration (for example Vite's API routing) belongs to the // exact consumer source evidence too, not just src/ and package files. inputs.push(directory.into()); + if who == "frontend" { + inputs.push("docker/frontend".into()); + } } let status = Command::new("git") .args(["status", "--porcelain", "--untracked-files=all", "--"]) diff --git a/tools/marreq-contracts/tests/contracts/artifacts.rs b/tools/marreq-contracts/tests/contracts/artifacts.rs index efb4cc3d..cbe7af4f 100644 --- a/tools/marreq-contracts/tests/contracts/artifacts.rs +++ b/tools/marreq-contracts/tests/contracts/artifacts.rs @@ -254,6 +254,8 @@ fn consumer_bundle_requires_committed_build_configuration() { "scripts/bundle-openapi.mjs", "package.json", "package-lock.json", + ".dockerignore", + "docker/frontend/nginx.conf.template", ] { let path = root.join(relative); std::fs::create_dir_all(path.parent().unwrap()).unwrap(); From 1e28dfa87ff1307df8312100564f57f6274b4d68 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 14:54:09 +0100 Subject: [PATCH 17/18] fix(contracts): validate bundled documents before selection and export --- docs/developer/http-api-contract.md | 4 +++- tools/marreq-contracts/src/artifact.rs | 5 ++-- tools/marreq-contracts/src/main.rs | 1 + tools/marreq-contracts/tests/contracts/cli.rs | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/developer/http-api-contract.md b/docs/developer/http-api-contract.md index 083fb6c6..ddfadde3 100644 --- a/docs/developer/http-api-contract.md +++ b/docs/developer/http-api-contract.md @@ -337,7 +337,9 @@ scripts/test-api-contracts.sh `validate` checks document/policy validity and requires a verified schema for **every discovered consumer dependency**. `--document-only` checks the document without certifying consumer coverage; it cannot create a consumer release bundle. -`select` and type generation require verified consumer contracts too; there is +`select` validates the bundled document with the pinned standards engine before +selection/type generation; consumer export validates its selected document too. +Both require verified consumer contracts; there is no development bypass for an unverified consumer operation. When adding/changing an HTTP call, update the actual Rocket implementation and diff --git a/tools/marreq-contracts/src/artifact.rs b/tools/marreq-contracts/src/artifact.rs index 8abceb6a..6fa404cc 100644 --- a/tools/marreq-contracts/src/artifact.rs +++ b/tools/marreq-contracts/src/artifact.rs @@ -190,9 +190,8 @@ pub fn export(root: &Path, who: &str, version: Version, mode: Option) -> coverage::scan(root, who, &spec)?.operations }; let selected = contract::select(&spec, &required, mode.as_deref())?; - if who == "backend" { - contract::validate(&selected)?; - } else { + contract::validate(&selected)?; + if who != "backend" { contract::validate_wire_contract(&selected)?; } let directory = if who == "mcp" { "mcp-server" } else { who }; diff --git a/tools/marreq-contracts/src/main.rs b/tools/marreq-contracts/src/main.rs index 9bc0ef5a..4c670314 100644 --- a/tools/marreq-contracts/src/main.rs +++ b/tools/marreq-contracts/src/main.rs @@ -133,6 +133,7 @@ fn run() -> anyhow::Result { output, } => { let spec = source::load(&spec)?; + contract::validate(&spec)?; let coverage = coverage::scan(&root, &component, &spec)?; contract::write( &output, diff --git a/tools/marreq-contracts/tests/contracts/cli.rs b/tools/marreq-contracts/tests/contracts/cli.rs index 26464a76..7b6b5584 100644 --- a/tools/marreq-contracts/tests/contracts/cli.rs +++ b/tools/marreq-contracts/tests/contracts/cli.rs @@ -18,3 +18,27 @@ fn cli_exit_codes_and_json_diagnostics() { assert_eq!(body["compatible"], !bad); } } + +#[test] +fn type_selection_rejects_invalid_source_documents_before_discovery() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("root.yaml"); + let mut document = super::fixtures::spec(); + document["info"]["title"] = Value::Null; + marreq_contracts::contract::write(&path, &document).unwrap(); + let output = dir.path().join("selected.json"); + let result = Command::new(env!("CARGO_BIN_EXE_marreq-contracts")) + .args(["select", "--component", "frontend", "--spec"]) + .arg(path) + .arg("--output") + .arg(&output) + .output() + .unwrap(); + assert!(!result.status.success()); + assert!(!output.exists()); + let body: Value = serde_json::from_slice(&result.stdout).unwrap(); + assert!( + body.to_string().contains("OpenAPI validation failed"), + "{body}" + ); +} From b5664d93c8067ebaf28099a09784c9e131dc5ce7 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Thu, 5 Nov 2026 15:00:22 +0100 Subject: [PATCH 18/18] test(docker): assert absence of each analysis tool explicitly --- .github/workflows/marreq-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/marreq-ci.yml b/.github/workflows/marreq-ci.yml index 9c0edc5a..d31ffed2 100644 --- a/.github/workflows/marreq-ci.yml +++ b/.github/workflows/marreq-ci.yml @@ -212,9 +212,9 @@ jobs: docker run --rm --entrypoint sh ${{ matrix.binary }}:ci -ec ' test ! -e /app/tools test ! -e /app/docs - ! command -v marreq-contracts - ! command -v oasdiff - ! command -v node + for tool in marreq-contracts oasdiff node; do + if command -v "$tool"; then exit 1; fi + done ' mcp-tests: