From 73362dc0ae32234938d3a3d5c90e3f71c8c6809e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 06:47:06 +0200 Subject: [PATCH 01/14] chore(deps): take the weekly production and development updates Production: the AWS SDK S3 client and lib-storage 3.1152.0, Prisma (client, adapter-pg, CLI) 7.10.1, TanStack Query 5.104.3 and Virtual 3.14.21, dotenv 18.0.8, jspdf-autotable 5.0.9, lucide-react 1.59.0, nodemailer 10.1.2, pg-boss 12.38.0, React and React DOM 19.3.1 (still exact) and undici 8.12. React 19.3.1 and undici 8.12.2 are security releases. Prisma 7.10.1 stops a nested interactive transaction from leaking its id into a later top-level one. pg-boss moves its schema from 45 to 46; the worker applies the migration on start and the index builds continue in the background. Its list and delete helpers now page and batch, which touches only the queue retirement at boot, and that is idempotent. The retention test pins the installed version, so it moves to 12.38.0 after re-checking every source line it asserts. Development: @axe-core/playwright 4.14.0, Playwright 1.64.1, Vitest and its coverage provider 5.0.5, shadcn 4.22.0. The Stryker runner patch still applies; it touches the runner's own name join, and 5.0.5 changes nothing in how Vitest builds a test's full name. axe-core 4.14 turns on label-content-name-mismatch for the WCAG 2.1 A tag the a11y specs run. The S3 client is held on the same release as lib-storage rather than the newest one its range allows, so the two share one SDK core. Vite gets a floor at 8.1.6 for GHSA-587h-7wmj-h5pm and four sibling advisories against its dev server and bundled launch-editor. It arrives only under Vitest and never reaches the image. The image's separate Prisma CLI install moves with the app tree to 7.10.1, and its dotenv to 18.0.8, which it had trailed by a patch. --- Dockerfile | 2 +- package.json | 40 +- pnpm-lock.yaml | 2141 +++++++++-------- pnpm-workspace.yaml | 7 + .../__tests__/job-failures-retention.test.ts | 2 +- 5 files changed, 1139 insertions(+), 1053 deletions(-) diff --git a/Dockerfile b/Dockerfile index daba7469c..377c19f6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -268,7 +268,7 @@ RUN mkdir -p /opt/prisma-cli && \ npm pkg set 'overrides.mysql2=^3.22.0' && \ npm pkg set 'overrides.@hono/node-server=^1.19.15' && \ npm pkg set 'overrides.valibot=^1.4.2' && \ - npm install --omit=dev prisma@7.10.0 @prisma/engines@7.10.0 tsx@4.23.1 dotenv@18.0.6 && \ + npm install --omit=dev prisma@7.10.1 @prisma/engines@7.10.1 tsx@4.23.1 dotenv@18.0.8 && \ ln -sfn /opt/prisma-cli/node_modules/.bin/tsx /usr/local/bin/healthlog-tsx && \ ln -sfn /opt/prisma-cli/node_modules/dotenv /app/node_modules/dotenv && \ ln -sfn /opt/prisma-cli/node_modules/prisma /app/node_modules/prisma && \ diff --git a/package.json b/package.json index 5d19a1764..2f3026328 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "mcp:stdio": "npx tsx src/cli/mcp-stdio.ts" }, "dependencies": { - "@aws-sdk/client-s3": "^3.1150.0", - "@aws-sdk/lib-storage": "3.1150.0", + "@aws-sdk/client-s3": "^3.1152.0", + "@aws-sdk/lib-storage": "3.1152.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -62,42 +62,42 @@ "@napi-rs/canvas": "1.0.11", "@node-rs/argon2": "^2.2.2", "@parse/node-apn": "^8.1.0", - "@prisma/adapter-pg": "^7.10.0", - "@prisma/client": "^7.10.0", + "@prisma/adapter-pg": "^7.10.1", + "@prisma/client": "^7.10.1", "@simplewebauthn/browser": "^14.0.0", "@simplewebauthn/server": "^14.0.3", - "@tanstack/react-query": "5.104.2", - "@tanstack/react-virtual": "^3.14.17", + "@tanstack/react-query": "5.104.3", + "@tanstack/react-virtual": "^3.14.21", "@types/sax": "^1.2.7", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.4.0", - "dotenv": "^18.0.7", + "dotenv": "^18.0.8", "fflate": "^0.8.3", "jose": "^6.2.12", "jspdf": "^4.2.1", - "jspdf-autotable": "^5.0.8", - "lucide-react": "^1.56.0", + "jspdf-autotable": "^5.0.9", + "lucide-react": "^1.59.0", "mmdb-lib": "^3.0.3", "next": "16.3.8", - "nodemailer": "10.1.0", + "nodemailer": "10.1.2", "otpauth": "^9.5.2", "p-limit": "^7.3.3", "pg": "^8.23.1", - "pg-boss": "^12.37.1", - "prisma": "^7.10.0", + "pg-boss": "^12.38.0", + "prisma": "^7.10.1", "qrcode": "^1.5.4", "radix-ui": "^1.8.1", - "react": "19.3.0", + "react": "19.3.1", "react-day-picker": "^10.0.2", - "react-dom": "19.3.0", + "react-dom": "19.3.1", "recharts": "^3.10.1", "rrule": "^2.8.1", "sax": "^1.6.1", "sonner": "^2.0.8", "tailwind-merge": "^3.7.0", "tesseract.js": "^7.0.0", - "undici": "^8.11.2", + "undici": "^8.12.2", "web-push": "^3.6.7", "yaml": "^2.9.1", "zod": "^4.6.7", @@ -105,9 +105,9 @@ "zxcvbn-typescript": "^5.0.1" }, "devDependencies": { - "@axe-core/playwright": "^4.13.0", + "@axe-core/playwright": "^4.14.0", "@next/bundle-analyzer": "^16.3.8", - "@playwright/test": "^1.64.0", + "@playwright/test": "^1.64.1", "@stryker-mutator/core": "10.0.0", "@stryker-mutator/vitest-runner": "10.0.0", "@tailwindcss/postcss": "^4", @@ -120,7 +120,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "@types/web-push": "^3.6.4", - "@vitest/coverage-v8": "^5.0.4", + "@vitest/coverage-v8": "^5.0.5", "ajv": "^8.20.0", "eslint": "^9", "eslint-config-next": "16.3.8", @@ -131,11 +131,11 @@ "pdfjs-dist": "6.4.299", "prettier": "^3.9.10", "prettier-plugin-tailwindcss": "^0.8.1", - "shadcn": "^4.21.4", + "shadcn": "^4.22.0", "tailwindcss": "^4", "testcontainers": "^12.2.1", "tw-animate-css": "^1.4.0", "typescript": "^6", - "vitest": "^5.0.4" + "vitest": "^5.0.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aee6822ad..3d648306e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,7 @@ overrides: brace-expansion@>=2.0.0 <2.1.7: ^2.1.7 brace-expansion@>=4.0.0 <5.0.12: ^5.0.12 undici@>=7.0.0 <7.29.1: ^7.29.1 + vite@>=8.0.0 <8.1.6: ^8.1.6 patchedDependencies: '@stryker-mutator/vitest-runner@10.0.0': 9ad3e9855fd9781fd36e872ed42c5f934f91b962d6830e6cc56d56bf16e1dabd @@ -45,20 +46,20 @@ importers: .: dependencies: '@aws-sdk/client-s3': - specifier: ^3.1150.0 - version: 3.1150.0 + specifier: ^3.1152.0 + version: 3.1152.0 '@aws-sdk/lib-storage': - specifier: 3.1150.0 - version: 3.1150.0(@aws-sdk/client-s3@3.1150.0) + specifier: 3.1152.0 + version: 3.1152.0(@aws-sdk/client-s3@3.1152.0) '@dnd-kit/core': specifier: ^6.3.1 - version: 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) '@dnd-kit/sortable': specifier: ^10.0.0 - version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react@19.3.1) '@dnd-kit/utilities': specifier: ^3.2.2 - version: 3.2.2(react@19.3.0) + version: 3.2.2(react@19.3.1) '@modelcontextprotocol/sdk': specifier: ^1.32.1 version: 1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@4.6.7) @@ -72,11 +73,11 @@ importers: specifier: ^8.1.0 version: 8.1.0(supports-color@7.2.0) '@prisma/adapter-pg': - specifier: ^7.10.0 - version: 7.10.0 + specifier: ^7.10.1 + version: 7.10.1 '@prisma/client': - specifier: ^7.10.0 - version: 7.10.0(prisma@7.10.0(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@6.0.3))(typescript@6.0.3) + specifier: ^7.10.1 + version: 7.10.1(prisma@7.10.1(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3) '@simplewebauthn/browser': specifier: ^14.0.0 version: 14.0.0 @@ -84,11 +85,11 @@ importers: specifier: ^14.0.3 version: 14.0.3 '@tanstack/react-query': - specifier: 5.104.2 - version: 5.104.2(react@19.3.0) + specifier: 5.104.3 + version: 5.104.3(react@19.3.1) '@tanstack/react-virtual': - specifier: ^3.14.17 - version: 3.14.17(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + specifier: ^3.14.21 + version: 3.14.21(react-dom@19.3.1(react@19.3.1))(react@19.3.1) '@types/sax': specifier: ^1.2.7 version: 1.2.7 @@ -102,8 +103,8 @@ importers: specifier: ^4.4.0 version: 4.4.0 dotenv: - specifier: ^18.0.7 - version: 18.0.7 + specifier: ^18.0.8 + version: 18.0.8 fflate: specifier: ^0.8.3 version: 0.8.3 @@ -114,20 +115,20 @@ importers: specifier: ^4.2.1 version: 4.2.1 jspdf-autotable: - specifier: ^5.0.8 - version: 5.0.8(jspdf@4.2.1) + specifier: ^5.0.9 + version: 5.0.9(jspdf@4.2.1) lucide-react: - specifier: ^1.56.0 - version: 1.56.0(@types/react@19.3.0)(react@19.3.0) + specifier: ^1.59.0 + version: 1.59.0(@types/react@19.3.0)(react@19.3.1) mmdb-lib: specifier: ^3.0.3 version: 3.0.3 next: specifier: 16.3.8 - version: 16.3.8(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.0)(@types/node@26.6.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 16.3.8(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.1)(@types/node@26.6.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) nodemailer: - specifier: 10.1.0 - version: 10.1.0 + specifier: 10.1.2 + version: 10.1.2 otpauth: specifier: ^9.5.2 version: 9.5.2 @@ -138,29 +139,29 @@ importers: specifier: ^8.23.1 version: 8.23.1 pg-boss: - specifier: ^12.37.1 - version: 12.37.1(@opentelemetry/api@1.9.1) + specifier: ^12.38.0 + version: 12.38.0(@opentelemetry/api@1.9.1) prisma: - specifier: ^7.10.0 - version: 7.10.0(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@6.0.3) + specifier: ^7.10.1 + version: 7.10.1(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) qrcode: specifier: ^1.5.4 version: 1.5.4 radix-ui: specifier: ^1.8.1 - version: 1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) react: - specifier: 19.3.0 - version: 19.3.0 + specifier: 19.3.1 + version: 19.3.1 react-day-picker: specifier: ^10.0.2 - version: 10.0.2(@types/react@19.3.0)(react@19.3.0) + version: 10.0.2(@types/react@19.3.0)(react@19.3.1) react-dom: - specifier: 19.3.0 - version: 19.3.0(react@19.3.0) + specifier: 19.3.1 + version: 19.3.1(react@19.3.1) recharts: specifier: ^3.10.1 - version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@16.13.1)(react@19.3.0)(redux@5.0.1) + version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@16.13.1)(react@19.3.1)(redux@5.0.1) rrule: specifier: ^2.8.1 version: 2.8.1 @@ -169,7 +170,7 @@ importers: version: 1.6.1 sonner: specifier: ^2.0.8 - version: 2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 2.0.8(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) tailwind-merge: specifier: ^3.7.0 version: 3.7.0 @@ -177,8 +178,8 @@ importers: specifier: ^7.0.0 version: 7.0.0 undici: - specifier: ^8.11.2 - version: 8.11.2 + specifier: ^8.12.2 + version: 8.12.3 web-push: specifier: ^3.6.7 version: 3.6.7(supports-color@7.2.0) @@ -196,20 +197,20 @@ importers: version: 5.0.1 devDependencies: '@axe-core/playwright': - specifier: ^4.13.0 - version: 4.13.0(playwright-core@1.64.0) + specifier: ^4.14.0 + version: 4.14.0(playwright-core@1.64.1) '@next/bundle-analyzer': specifier: ^16.3.8 version: 16.3.8 '@playwright/test': - specifier: ^1.64.0 - version: 1.64.0 + specifier: ^1.64.1 + version: 1.64.1 '@stryker-mutator/core': specifier: 10.0.0 version: 10.0.0(@types/node@26.6.5) '@stryker-mutator/vitest-runner': specifier: 10.0.0 - version: 10.0.0(patch_hash=9ad3e9855fd9781fd36e872ed42c5f934f91b962d6830e6cc56d56bf16e1dabd)(@stryker-mutator/core@10.0.0(@types/node@26.6.5))(vitest@5.0.4) + version: 10.0.0(patch_hash=9ad3e9855fd9781fd36e872ed42c5f934f91b962d6830e6cc56d56bf16e1dabd)(@stryker-mutator/core@10.0.0(@types/node@26.6.5))(vitest@5.0.5) '@tailwindcss/postcss': specifier: ^4 version: 4.3.3 @@ -241,8 +242,8 @@ importers: specifier: ^3.6.4 version: 3.6.4 '@vitest/coverage-v8': - specifier: ^5.0.4 - version: 5.0.4(vitest@5.0.4) + specifier: ^5.0.5 + version: 5.0.5(vitest@5.0.5) ajv: specifier: ^8.20.0 version: 8.20.0 @@ -274,8 +275,8 @@ importers: specifier: ^0.8.1 version: 0.8.1(prettier@3.9.10) shadcn: - specifier: ^4.21.4 - version: 4.21.4(@cfworker/json-schema@4.1.1)(supports-color@7.2.0) + specifier: ^4.22.0 + version: 4.22.0(@cfworker/json-schema@4.1.1)(supports-color@7.2.0) tailwindcss: specifier: ^4 version: 4.3.3 @@ -289,8 +290,8 @@ importers: specifier: ^6 version: 6.0.3 vitest: - specifier: ^5.0.4 - version: 5.0.4(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + specifier: ^5.0.5 + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) packages: @@ -298,70 +299,70 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@aws-sdk/checksums@3.1001.3': - resolution: {integrity: sha512-CcAkVyEUKGngbEqJdVdSc53Iy52+XzVL5NIcTen2BlK8UByNC1DThVtf5VKSrNzyA+SqyTvs+3S+knVBbOUizQ==} + '@aws-sdk/checksums@3.1001.4': + resolution: {integrity: sha512-H8BiG/2DnfwydVxD1uDPLa1s1sfhRnVTbD2OwzfTr3WlJzgbIzICDXxBTlTMjbWNNCe4VS9X55fco+JI34L9kw==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-s3@3.1150.0': - resolution: {integrity: sha512-tv7KzK7PY7GqBwA6nzAVAsazIY/tAagDjlqvmCwlIYpH2SMfUuD6pJklg++LGcMaNy5p0VMvz+F+/es1BpoyRw==} + '@aws-sdk/client-s3@3.1152.0': + resolution: {integrity: sha512-oI1+ZaM6pSOwZazu4nAb7rL8ug7LSjg31tDoJqRB9MooRqqWCf/qvkIZqne1mRA3xK3slg6K0RNWXaJYHbWLvg==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.978.3': - resolution: {integrity: sha512-nslpzUCTI9b2IQMWSd6mSAXldBexPc8rr0d3dXUutXYFLFaTdsHa2GD7sJdsn3PhyjK0O5K8ypd6zi+RXXPVlg==} + '@aws-sdk/core@3.978.4': + resolution: {integrity: sha512-JvGp6y9sPX8FSs2K/EmFMZcUfdkHxqGE45+oxwGC75OIh50j2e4cbx7zcR3BwNs2DSGqJdBja5h6Vy89iIDmRQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.74': - resolution: {integrity: sha512-rsRJcW2iFUhCgclyD6OSbMEaPl8Eu0qSeRZb2uuCK1ViZ66hl5wOe/p6Vr6MO3b+ydyKgFeqza2p6tZThvILlw==} + '@aws-sdk/credential-provider-env@3.972.75': + resolution: {integrity: sha512-nc8Epg98PcYoKNx0VFlwtmZSCP+tuJ1FLWqpslET/gp4h/WYuULSJqtxppM6ywKKDdzNJPvLD7ceRfmxm3Qdxw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.76': - resolution: {integrity: sha512-pnKdh0Br7OJPj+7qKjzgTfDTFW0RhEMKXNDR2jv9CAM4oHuG/4z3hRh8KtutLfpmItuRtDbKBmwWkDqHwwAWXw==} + '@aws-sdk/credential-provider-http@3.972.77': + resolution: {integrity: sha512-KkYFdIQwuw9IOHfEzK78slxmVWuhcm1w6YgE3V3WVxAeRRj9q5J9jlwzpY/oszWxd4SD96FTFkDdRGBdMFF2Mw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.19': - resolution: {integrity: sha512-kcjvAxqQEV8yuwkX58RelNn3ne65o7JgRWf3Z/gVvbi7Xuw1qSDLJlatrLD76+40cmRxmNkLxBoyp/vAQChQ5g==} + '@aws-sdk/credential-provider-ini@3.973.20': + resolution: {integrity: sha512-OSl2R/0zLkoeKEIy6oP2tu/bsSdY+DGgro2eOffMD2CcP8POv7rS14FY5Jzc5bjnqB1G9wv/aM1akHC2mdlhZw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.81': - resolution: {integrity: sha512-CeQHSbE2CnVdaHIJ/NayRaCecIER+Pm9faUPkmRATO54rCoMTEPSbVieGHzEwvm1dZM2HIs6qQZCznhayNTWJQ==} + '@aws-sdk/credential-provider-login@3.972.82': + resolution: {integrity: sha512-8jyDPtf1WPxeQ/5xlNpyxAD6P7YKwkYeNk8B2vFEDwkPWDvb7c3tQJG5/jbNWbF2yTMIH9tDTueBkdfovYIAPw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.86': - resolution: {integrity: sha512-WNttSyr6e8innPtBSty//CdnqJcgZN6eAbtw3v/CR2IObz6MYZEo8eusdtuCHJ2ujwFqC2bSPHVyhXGHx8HruQ==} + '@aws-sdk/credential-provider-node@3.972.87': + resolution: {integrity: sha512-TKj2C0x4IMPSSOAlmeryX8fCPvvPS2pFJz2ikV9IGqyh6Ehsg4leMalCTVfV2rvMTrlGAe7X7nK0HZ3P0KZR9g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.74': - resolution: {integrity: sha512-Yj6kZFJQdbR+PygTkqdVLPHHpdDpOS9GCYVcDCuweCRRig9LF24ELQBMfyUw1vspvrZJUfnOGVyMjhaL094ahw==} + '@aws-sdk/credential-provider-process@3.972.75': + resolution: {integrity: sha512-ZNm8T/HJNhpS8MikDVAsym8QyUSUuOUGiCkmI3TTya7G6xhOgfRLd2iK91HhQQyvRM36eo6Xm845hHVyfToA2w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.18': - resolution: {integrity: sha512-FnpPhdY4qUGtX/8X6eKxh+uD+9Y19i9FYibgk4SBe0M935s9vocHkHzaxaMNk+YkHmvzgdu8cHA5SCoeU+kkew==} + '@aws-sdk/credential-provider-sso@3.973.19': + resolution: {integrity: sha512-qHGDAGesyWtRw3BapyFkYSUzVNrC1CWPS6+S2lf/MLMEkWB2gR3Bl6imVHy1nhImXKS+gcuUDzbu39BnsHJc1Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.80': - resolution: {integrity: sha512-TJF25Ndwy2HxcVj6oxMgp8qVlJAawlExHYBXqhuC6Zt2m+A7WpdkjBD6QglNbiArlteYljZHEY3R7KKmno6ISA==} + '@aws-sdk/credential-provider-web-identity@3.972.81': + resolution: {integrity: sha512-nHkgkMIC6NHgqfI6UAQq5cCS4bUkNxkT+r9fXCq/X4IKfX/DHGZ8UM3T4b78OXpWK5OlzEzN4kxwyax0W/MUCA==} engines: {node: '>=20.0.0'} - '@aws-sdk/lib-storage@3.1150.0': - resolution: {integrity: sha512-ucHABAJADcOkNEmQtaHkqq7kSBe7yZ5+gudhEfiN1NxDsz2WpON1N2NpbENsu4oWxH4Ueq4wqEIbBUt8zbEFbw==} + '@aws-sdk/lib-storage@3.1152.0': + resolution: {integrity: sha512-UU1fDLKBd8az+4AmNx30ggnA4D4HolUd1MxT/J6ka76qKcckI0IxjAy6FpX/vwiUf2KDNxxN2YX0cfwjMqzWoQ==} engines: {node: '>=20.0.0'} peerDependencies: - '@aws-sdk/client-s3': ^3.1150.0 + '@aws-sdk/client-s3': ^3.1152.0 - '@aws-sdk/middleware-sdk-s3@3.972.79': - resolution: {integrity: sha512-UfgAQosf4IEXldMsyntVuHsJnaQzlH6pmkOr6dQy7CaOYSlGz3mWiW4Di7XfQ/kPJP6xE7oMshz1V0E8RqGdRg==} + '@aws-sdk/middleware-sdk-s3@3.972.80': + resolution: {integrity: sha512-zX3lXN/9XOUAuGpxgt3wXwZrXhTjQYEgRsqa856PbZWpOtleUWmCMbhlkLfusPuhW24cOC/8JYR8hnRalrrIkQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.48': - resolution: {integrity: sha512-B01jdXJtAW/XOiUzGdI2mz67WyEHbGEc9X6Euzw3bjLAa+txTqdZDER4X2FsIEcXzV2ABqudVZ5thali8tCCzQ==} + '@aws-sdk/nested-clients@3.997.49': + resolution: {integrity: sha512-zjqIqzTYLRKhT7TPJDUreK9vG9PjhmnfLCaTaqArkRT7j+Ogl0tuXB7MpSd8rlRfRT+xTOeyNFWaOfyNcf51hg==} engines: {node: '>=20.0.0'} '@aws-sdk/signature-v4-multi-region@3.996.48': resolution: {integrity: sha512-cFDXfEbXaGwbnpvR/DyqfxfjxpjXVRVthQLgkwvcfDS5u3+8G9WSj1X1Ag/pTRg/QOeOno1Tq3ycEdrKsCUcOA==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1150.0': - resolution: {integrity: sha512-wDU36ofhabZYu+d2KSriyDbwzxNoaOp+4dG0p3qkabk7HeALR5nIWyQbjoyJZLn3iqWkv/xNdyUfbfevDVDzzg==} + '@aws-sdk/token-providers@3.1153.0': + resolution: {integrity: sha512-6bKJ2OfLboxhzeJ3hBDeB8L6emCYf9DfdrjBAharLrY42J1RV/6jA6G8jvfNghzID8Yb40BdSY7jQMtWx9Ot0Q==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.974.6': @@ -376,8 +377,9 @@ packages: resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} - '@axe-core/playwright@4.13.0': - resolution: {integrity: sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==} + '@axe-core/playwright@4.14.0': + resolution: {integrity: sha512-SGUhG5PIHd2eMdpgBF2PyQLOhX+qyDR2PT4cmoS4Qk2ak+jX/rrSvst9l0PGv5WTHolKpUMvVmoHpr4cZgf1RQ==} + engines: {node: '>=22.12.0'} peerDependencies: playwright-core: '>= 1.0.0' @@ -762,18 +764,12 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} @@ -1693,12 +1689,12 @@ packages: cpu: [x64] os: [win32] - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} - '@oxc-project/types@0.153.0': resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} + '@oxc-project/types@0.154.0': + resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] @@ -1869,22 +1865,22 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.64.0': - resolution: {integrity: sha512-oTN9FNYuN6bqQMwiho6jJKLCG2/QOYCixGXuCjRmzOq23eODeukuY2XiAVoVpAECbuezMt1MHQjAiQxK8F+Y+g==} + '@playwright/test@1.64.1': + resolution: {integrity: sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==} engines: {node: '>=20'} hasBin: true '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/adapter-pg@7.10.0': - resolution: {integrity: sha512-N7nwSor0HO1Kz6xBv0TPAjAPysKK0fac6p4fVN3ensLOuzc/83Fgmln5k92eK/cvzqdkSR/2kkAqlbcdwVrwpw==} + '@prisma/adapter-pg@7.10.1': + resolution: {integrity: sha512-EP9ruPYHjKP80WZlb5qVaEXKgZjJieBFjAxcBtQnDAHD6+16UN4keVmBh6L/RUpuDQT2FQRbvKNgq5d4Ix+IkQ==} - '@prisma/client-runtime-utils@7.10.0': - resolution: {integrity: sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==} + '@prisma/client-runtime-utils@7.10.1': + resolution: {integrity: sha512-AzoATVIgmO3lcZfM443+9SI7+cyLMW2+Psw4op8WQHpHvHCX//CKxv8fwhiL0yGWF8DjdQrFewxrZo5McBdB8g==} - '@prisma/client@7.10.0': - resolution: {integrity: sha512-Ubw/QS9JGIBSBUsyxAUQuK/Jcu0Tsva7le7QbLd91Kix9yJvYDdj5QkwgEbbZniH80dd+sziQcALPc+HnvQC8Q==} + '@prisma/client@7.10.1': + resolution: {integrity: sha512-5tgAbFGh//zVOC6WPNVgziOErJHYkUE+wmqCwbIyq6IrTXUYtv9y2iBFGh/Owo99Mx0+cr0j1FOrxkYBMuyhaQ==} engines: {node: ^20.19 || ^22.12 || >=24.0} peerDependencies: prisma: '*' @@ -1895,11 +1891,11 @@ packages: typescript: optional: true - '@prisma/config@7.10.0': - resolution: {integrity: sha512-Rcg828gIRE3HOQ3pOATFjV5d/P0U9OIobxhd/IMxlfWjA4vru0eGwb0AIwFw0rmcLMVShohZYWPixVxkBHsxUA==} + '@prisma/config@7.10.1': + resolution: {integrity: sha512-+0swF5ELUa3P/9cIR26+gpYjjCbGE0qvEBZhV4zMDkYsrtHjZ884MQbOnWFWXj600N0sSJNnHbtBqe12yDngWg==} - '@prisma/debug@7.10.0': - resolution: {integrity: sha512-caygJKtltmRIgdJ3jRpkOr7yM4DW6zxo5uOmojKWFb3asnxWoRkQOwZmXBgD8FZp4htrX+nMpcWqDwzlQ1+Y4g==} + '@prisma/debug@7.10.1': + resolution: {integrity: sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==} '@prisma/debug@7.2.0': resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} @@ -1907,20 +1903,20 @@ packages: '@prisma/dev@0.24.17': resolution: {integrity: sha512-UvdZzmpFwknnfreh6Jije84ekkYGPYEJhXG1tFzCsCfQyzJifrOo38eZc0qajzvaC6OLUOrN9ML5XfCnEZL9DA==} - '@prisma/driver-adapter-utils@7.10.0': - resolution: {integrity: sha512-u8zkcRLlaryO652T4qavBg0HmzNW5tSKdsCn6hc1PhWAp/J6k0vrxLuUs+b9o+HcjsK7Dfa01o4OFSn0frauJA==} + '@prisma/driver-adapter-utils@7.10.1': + resolution: {integrity: sha512-DJ4c6xvVnm3s9ZUyql9nCUicADSqyvH+CjXRCM9L6m5zJxCNmebCElG6X4irICq3rosBwlX+S19Dxq/4IrZk3A==} - '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': - resolution: {integrity: sha512-8OJ6RuZTZ06eFUOtBwxVmv8XMmOW6HWN5F+uxUbZkGxR0Bfab1dfAdXaHPmR5mb59E+fmUo8IOzXlbLY1SClbw==} + '@prisma/engines-version@8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8': + resolution: {integrity: sha512-XjXmwjN2NE340RpXoXv8ZF8aNAvr0FE+7gNfkKeW6vyjXs7F8uxvEbWQoVrhioXDQoIsnNmv7r+wxvzfdEZaWA==} - '@prisma/engines@7.10.0': - resolution: {integrity: sha512-KNumN6NHFwybvfdYzTee9pqwx5PvknpWAaHn6L5NsbrKdl+SQrsVZs9opKs6U6SAsvB26HDt3WybRjOhgoWOYQ==} + '@prisma/engines@7.10.1': + resolution: {integrity: sha512-R6/ghv1Bu36FhCdQLFof1pNtyoWoeDzJLYeTKjgqb4eGtOAJx7ZqJtcm4pzkahLfZ9rYz8AkmsMDwkyiYbXPCQ==} - '@prisma/fetch-engine@7.10.0': - resolution: {integrity: sha512-Zqyu8DY14t6W/xwmAxUYWCXtHrvQnSvT644EAZSsdM8NSmCS74vJJbBKdVsK3ucFpnUWkEpbO1a0CxJXrg130g==} + '@prisma/fetch-engine@7.10.1': + resolution: {integrity: sha512-qZezeaExLWG2ptbHOrMxdiWluKHgqEclTdJbvY6pk6+Wg7EiVkeq5U3cXz9ti8nd0bngNvsDgledLHVRg5LF1w==} - '@prisma/get-platform@7.10.0': - resolution: {integrity: sha512-0bra1LFYi8xNw0yqV62bHJNQk4BKleOngZiqPWIQc7a3+9q6rqsnqJ15BuepP8943PFMvtmgnP52juZsyYkA6w==} + '@prisma/get-platform@7.10.1': + resolution: {integrity: sha512-iVnghHne5vcaobBjPVCMEjh5r/1UCj394EIGTUclasFRatpR/MQwcM2veLv5kgoFSyYP54nch/lNFCQRYcDxvg==} '@prisma/get-platform@7.2.0': resolution: {integrity: sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==} @@ -2742,97 +2738,98 @@ packages: react-redux: optional: true - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@rolldown/binding-android-arm-eabi@1.2.14': + resolution: {integrity: sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.14': + resolution: {integrity: sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@rolldown/binding-darwin-arm64@1.2.14': + resolution: {integrity: sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@rolldown/binding-darwin-x64@1.2.14': + resolution: {integrity: sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@rolldown/binding-freebsd-x64@1.2.14': + resolution: {integrity: sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': + resolution: {integrity: sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@rolldown/binding-linux-arm64-gnu@1.2.14': + resolution: {integrity: sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@rolldown/binding-linux-arm64-musl@1.2.14': + resolution: {integrity: sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + '@rolldown/binding-linux-ppc64-gnu@1.2.14': + resolution: {integrity: sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + '@rolldown/binding-linux-s390x-gnu@1.2.14': + resolution: {integrity: sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@rolldown/binding-linux-x64-gnu@1.2.14': + resolution: {integrity: sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@rolldown/binding-linux-x64-musl@1.2.14': + resolution: {integrity: sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@rolldown/binding-openharmony-arm64@1.2.14': + resolution: {integrity: sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@rolldown/binding-win32-arm64-msvc@1.2.14': + resolution: {integrity: sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@rolldown/binding-win32-x64-msvc@1.2.14': + resolution: {integrity: sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2846,8 +2843,8 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@shadcn/registry@0.1.3': - resolution: {integrity: sha512-82/OmKAVM9pChDHFVAsDP9PcimPGSKZGdbcdwdGGGcSI69ImpN4ZnqsfZ4hkOFEMal92t3y9djYJHMjO4PcVZA==} + '@shadcn/registry@0.2.0': + resolution: {integrity: sha512-m3TeqU/+av2kgOLfESHkWzsmhuQuY4NXZd5Y1Rd4lzy4IX3JJ5+k2mYfkq6OT8oMuk2W4GQPAkO7+CC7KlpSbw==} engines: {node: '>=20.18.1'} '@simplewebauthn/browser@14.0.0': @@ -2865,6 +2862,10 @@ packages: resolution: {integrity: sha512-ejTukBuP/Pg3t9N8Ux+0zKsXeHWm7p6W3HbXFOyltKasSxZwCFmvTut7kBdZSCF3m9y1bXXyVu6p2xuMFcsTyQ==} engines: {node: '>=18.0.0'} + '@smithy/core@3.36.1': + resolution: {integrity: sha512-WC5W1Jrsfxu1y6LeqXMie7N14qmcdafij/Haye4feVlyMMXLGpYxBeOX6Ee1iRJbDzGG5LVman8U1rrt9MYFHA==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.5.2': resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} engines: {node: '>=18.0.0'} @@ -3009,22 +3010,22 @@ packages: '@tailwindcss/postcss@4.3.3': resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - '@tanstack/query-core@5.104.2': - resolution: {integrity: sha512-/qbsxJK5c3Ve1aFIULWYNcT2jeRUAiD4HME6Eu2ZdpofHoVgAjpkQhLg5xHuOoBkTPQUJLfP7zOxy0qsBcqROA==} + '@tanstack/query-core@5.104.3': + resolution: {integrity: sha512-DZY9TuJAOgU3UyHUhXb4CcTYEHNZszlwkPg4VViIwTxOKzLprPUU+rbaPZQd3srqkGOtpaDdKpW972UV/LKlcg==} - '@tanstack/react-query@5.104.2': - resolution: {integrity: sha512-xKmzWGhadbE5wGS/ZmXShRITnI9oItox5GO14KvFZco3+vhTAyXS1erYzJWdiY280zapAGJXFUL9nHgwP6SV/g==} + '@tanstack/react-query@5.104.3': + resolution: {integrity: sha512-5Ts1ImwJKqoJgPkLxl9WrPQy1JEoEmti3MaaODCfBamHUGFk57IFrc+AIksPq+BAm10EsroX56qm398Z42LG7w==} peerDependencies: react: ^18 || ^19 - '@tanstack/react-virtual@3.14.17': - resolution: {integrity: sha512-b7b5LwydgQAWcsAWEZyd3iZkN5/QcC6S0K5E1zNheyFoSZl7E78foepGeDHlX2+JuXilA+VIGGFsUYPFddOrig==} + '@tanstack/react-virtual@3.14.21': + resolution: {integrity: sha512-wWE/ZJDNNDp18JHWy7n5PQooqerHePVkEEu6Op0twKQ2InF9DPhYMQptQY0syDEviMd3/r8D0OUvKJibDGm9HQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/virtual-core@3.19.2': - resolution: {integrity: sha512-Ch+e8t097FdTfuihfRwBMqz/OfRZKHOH/C213dbM1lXDZ8bv6r330Bcc9k9XCrP6NkoMYy0/ZCHcddOAPihW4w==} + '@tanstack/virtual-core@3.19.6': + resolution: {integrity: sha512-CsTW892z23gy7IyxYTTorVu1uZWB+0N349rSMgKz4rFLPpLza7F7TpcCKeI5iUAwYQCt8QUlOU67J+KXCxtpGw==} '@tesseract.js-data/deu@1.0.0': resolution: {integrity: sha512-3ter1p2gFAiCd5CMC+PodZ9YY2UDomkKj+ytlKeeZqOUmhLx1SDnYPf5zog0g5MBbxYveW+8omI3PUflqHfZ4Q==} @@ -3404,11 +3405,11 @@ packages: '@visx/vendor@4.0.0-alpha.0': resolution: {integrity: sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==} - '@vitest/coverage-v8@5.0.4': - resolution: {integrity: sha512-P5YcQY8Y6RmoE9xRXI38rT8pssbcubEzqtYTM2XQoxOXMDn04nRaTI1SM8qCl9/NfW3ag9GlzdSXu7IwO5Ft8A==} + '@vitest/coverage-v8@5.0.5': + resolution: {integrity: sha512-4KpkGdIrSqBihx/JyP/e+S+IKB98etg1XDQiLAxksLfcShnwi1VReGeOT9NI43hLHbknosKp/1CCLn9Zgcs8mg==} peerDependencies: - '@vitest/browser': 5.0.4 - vitest: 5.0.4 + '@vitest/browser': 5.0.5 + vitest: 5.0.5 peerDependenciesMeta: '@vitest/browser': optional: true @@ -3421,19 +3422,19 @@ packages: resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} engines: {node: '>=22'} - '@vitest/mocker@5.0.4': - resolution: {integrity: sha512-tqAlrYWce4iUxhr3ncNohejtCTOcHeE8XWrSNZY3OhPoDn01yeGv+gnaBy64ATp7o6pb03eIbBAjzmibpc9vIQ==} + '@vitest/mocker@5.0.5': + resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: msw: ^2.4.9 || ^3.0.0 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: ^8.1.6 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/spy@5.0.4': - resolution: {integrity: sha512-MZKk/gSkm2q+ICobMxCpH1mA+cCAWm8WV7UhxlqGjq/sJ3AGc1BqFOs5CRVXxH78wQtme9q/NAAgBquj4AcyMw==} + '@vitest/spy@5.0.5': + resolution: {integrity: sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==} abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -3617,6 +3618,10 @@ packages: resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} engines: {node: '>=4'} + axe-core@4.14.0: + resolution: {integrity: sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==} + engines: {node: '>=4'} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -3952,8 +3957,8 @@ packages: resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} engines: {node: '>= 14'} - cron-parser@5.10.1: - resolution: {integrity: sha512-pKRrRagItwk9rGIStcUyMxFX34x56zoX3KDf9HJ4ttwkXIK1qxK63EvXvEDmlxI9AdPJ+Y7TEKRftRlW5eSS7A==} + cron-parser@5.10.2: + resolution: {integrity: sha512-JfAYMY0LZaWQTuict8PhtF/ksSnq8xefSDmQUnnoqvkPYkU/y3UP0DTWzJKFBz0P1IXsDG8q0vY3zHkoFZOhvw==} engines: {node: '>=18'} cross-spawn@7.0.6: @@ -4183,8 +4188,8 @@ packages: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} - dotenv@18.0.7: - resolution: {integrity: sha512-QEvMEF8ca3GNZJJnAbOgxpj5ULhBvnMcURp+XKrtvqFNPgjBvYoswQ0Jl2cO7JzCjxZjJhr9ZuIEhlrRxMft/Q==} + dotenv@18.0.8: + resolution: {integrity: sha512-2KDeoFoK/zRtcnbunCDlLG5VWVMcyqeQc6iK65o231nfNAcuGrKoqUQfJFUtBI+QlShk5HKIHxwebBtUl3rEfA==} engines: {node: '>=12'} hasBin: true @@ -5153,8 +5158,8 @@ packages: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} - jspdf-autotable@5.0.8: - resolution: {integrity: sha512-Hy05N86yBO7CXBrnSLOge7i1ZYpKH2DjQ94iybaP7vBhSInjvRBgDc99ngKzSbSO8Jc98ZCally8I6n0tj2RJQ==} + jspdf-autotable@5.0.9: + resolution: {integrity: sha512-wIOG2ggjG6Y7qvqjgFACZZeu+oHxoxrhwCn1lTMxjPhojLVVvu2U2yS2srVCYl0CXe6pqiG/jQMIIS0CFn1ssw==} peerDependencies: jspdf: ^2 || ^3 || ^4 @@ -5267,30 +5272,60 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.32.0: resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.32.0: resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.32.0: resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.32.0: resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.32.0: resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} @@ -5298,6 +5333,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} @@ -5305,6 +5347,13 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} @@ -5312,6 +5361,13 @@ packages: os: [linux] libc: [glibc] + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} @@ -5319,22 +5375,45 @@ packages: os: [linux] libc: [musl] + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.32.0: resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -5401,8 +5480,8 @@ packages: resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - lucide-react@1.56.0: - resolution: {integrity: sha512-b+hqj30/trHBma5r1zlKb6yLewQp6tUtnz9loc3xdrYerPmvLYhlEbyPVCJ8DOg2/blBf6lwPLznftFVACka6A==} + lucide-react@1.59.0: + resolution: {integrity: sha512-5qgMyyCPU6OvnKpLS2t693+ZtiqA8oaSweWoUows0BLFKFf6V75m4+CptpmohZHpD5IuOVIBXHBBiv2vpLJmtw==} peerDependencies: '@types/react': '*' react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -5619,8 +5698,8 @@ packages: resolution: {integrity: sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==} engines: {node: '>=18'} - nodemailer@10.1.0: - resolution: {integrity: sha512-fXW8AulB25aOTuwfRYV6lqHyfz2VM4rr4SGXH03mE7x5znEUpzL5mu6anARUpZN7744Ziwkmr4AxSLPJsBQspg==} + nodemailer@10.1.2: + resolution: {integrity: sha512-rnQn9Rzh8RAwcp42MVsIvxbsv6YZ2a2/av+uyJ/LPU1+2QSlrRgFsWWZTKenyRUqkIl8urOI0c4+CEcmFdsvQw==} engines: {node: '>=20.0.0'} non-error@0.1.0: @@ -5841,8 +5920,8 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - pg-boss@12.37.1: - resolution: {integrity: sha512-LghWeOIbMCiLcTY3+8MInXaJ0wk0AOkbeUZ8oE+cGO0uoheGSW3GK57eDqEbrDGpwTIpX9BVphvdvWEDYTcJ9A==} + pg-boss@12.38.0: + resolution: {integrity: sha512-felA3fyPSJwWk5cb/VwAgnudCAzWEdgBjjf+eukXm8pExlwkhjdBbOMukMyG5s8uZHO6fZZZcaIaTa3oKpfSCg==} engines: {node: '>=22.12.0'} hasBin: true peerDependencies: @@ -5903,13 +5982,13 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} - playwright-core@1.64.0: - resolution: {integrity: sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==} + playwright-core@1.64.1: + resolution: {integrity: sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==} engines: {node: '>=20'} hasBin: true - playwright@1.64.0: - resolution: {integrity: sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==} + playwright@1.64.1: + resolution: {integrity: sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==} engines: {node: '>=20'} hasBin: true @@ -6033,8 +6112,8 @@ packages: resolution: {integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==} engines: {node: '>=18'} - prisma@7.10.0: - resolution: {integrity: sha512-o0ornyJOWgygVAzGCpr8PdXV8EJLHyVGDDUr/voBQt8Azzw8cYTByzzPGcA/m4tCkPcnJA8raEOv2CslsKhPEw==} + prisma@7.10.1: + resolution: {integrity: sha512-GoVeErHGGtsSjsflQ1r2BA3RKTXtxNraQJz8OLkLhoLr7OgCscdx4qw4WMM4HnERYc+a1cq2VnMNbep+vOjNcQ==} engines: {node: ^20.19 || ^22.12 || >=24.0} hasBin: true peerDependencies: @@ -6148,10 +6227,10 @@ packages: '@types/react': optional: true - react-dom@19.3.0: - resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} + react-dom@19.3.1: + resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} peerDependencies: - react: ^19.3.0 + react: ^19.3.1 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6198,8 +6277,8 @@ packages: '@types/react': optional: true - react@19.3.0: - resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} + react@19.3.1: + resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} engines: {node: '>=0.10.0'} readable-stream@2.3.8: @@ -6312,8 +6391,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + rolldown@1.2.14: + resolution: {integrity: sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6416,8 +6495,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.21.4: - resolution: {integrity: sha512-YYfn/hsHbRH2mGwnKBx9tk+DjTQH+RUMS5iCxjKzwzuJ5uoHPqLOFUCJSeX0sXfrcBSfgQaXiOLGKNLjlIQxxw==} + shadcn@4.22.0: + resolution: {integrity: sha512-QKR3XUnatWlgo8IbZqGHrpsxjgdsfpPSAvJvLntQqAxPEf5U2sNsjDYLAS2xmFrOkC9lpj5UpZbrh/BuHrE2CQ==} engines: {node: '>=20.18.1'} hasBin: true @@ -6869,8 +6948,8 @@ packages: resolution: {integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==} engines: {node: '>=20.18.1'} - undici@8.11.2: - resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} + undici@8.12.3: + resolution: {integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==} engines: {node: '>=22.19.0'} unicorn-magic@0.3.0: @@ -6958,13 +7037,13 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + vite@8.3.4: + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 + '@vitejs/devtools': ^0.7.1 esbuild: ^0.28.1 jiti: '>=1.21.0' less: ^4.0.0 @@ -7001,23 +7080,23 @@ packages: yaml: optional: true - vitest@5.0.4: - resolution: {integrity: sha512-z0WxZRV2Sg48wF0FSgMq3TFG8JS38/Qr96vZVZdX6+Em9raQTvnXQvdTFJe2m3rfyJnEHeHQ/snceIdi8D1pVg==} + vitest@5.0.5: + resolution: {integrity: sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==} engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.4 - '@vitest/browser-preview': 5.0.4 + '@vitest/browser-playwright': 5.0.5 + '@vitest/browser-preview': 5.0.5 '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.4 - '@vitest/coverage-v8': 5.0.4 - '@vitest/ui': 5.0.4 + '@vitest/coverage-istanbul': 5.0.5 + '@vitest/coverage-v8': 5.0.5 + '@vitest/ui': 5.0.5 happy-dom: '*' jsdom: '*' - vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + vite: ^8.1.6 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -7215,9 +7294,6 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.6.7: resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} @@ -7228,126 +7304,126 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@aws-sdk/checksums@3.1001.3': + '@aws-sdk/checksums@3.1001.4': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1150.0': + '@aws-sdk/client-s3@3.1152.0': dependencies: - '@aws-sdk/checksums': 3.1001.3 - '@aws-sdk/core': 3.978.3 - '@aws-sdk/credential-provider-node': 3.972.86 - '@aws-sdk/middleware-sdk-s3': 3.972.79 + '@aws-sdk/checksums': 3.1001.4 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/credential-provider-node': 3.972.87 + '@aws-sdk/middleware-sdk-s3': 3.972.80 '@aws-sdk/signature-v4-multi-region': 3.996.48 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/core@3.978.3': + '@aws-sdk/core@3.978.4': dependencies: '@aws-sdk/types': 3.974.6 '@aws-sdk/xml-builder': 3.972.41 '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/signature-v4': 5.7.4 '@smithy/types': 4.19.0 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.74': + '@aws-sdk/credential-provider-env@3.972.75': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.76': + '@aws-sdk/credential-provider-http@3.972.77': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.19': + '@aws-sdk/credential-provider-ini@3.973.20': dependencies: - '@aws-sdk/core': 3.978.3 - '@aws-sdk/credential-provider-env': 3.972.74 - '@aws-sdk/credential-provider-http': 3.972.76 - '@aws-sdk/credential-provider-login': 3.972.81 - '@aws-sdk/credential-provider-process': 3.972.74 - '@aws-sdk/credential-provider-sso': 3.973.18 - '@aws-sdk/credential-provider-web-identity': 3.972.80 - '@aws-sdk/nested-clients': 3.997.48 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/credential-provider-env': 3.972.75 + '@aws-sdk/credential-provider-http': 3.972.77 + '@aws-sdk/credential-provider-login': 3.972.82 + '@aws-sdk/credential-provider-process': 3.972.75 + '@aws-sdk/credential-provider-sso': 3.973.19 + '@aws-sdk/credential-provider-web-identity': 3.972.81 + '@aws-sdk/nested-clients': 3.997.49 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.81': + '@aws-sdk/credential-provider-login@3.972.82': dependencies: - '@aws-sdk/core': 3.978.3 - '@aws-sdk/nested-clients': 3.997.48 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/nested-clients': 3.997.49 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.86': + '@aws-sdk/credential-provider-node@3.972.87': dependencies: - '@aws-sdk/credential-provider-env': 3.972.74 - '@aws-sdk/credential-provider-http': 3.972.76 - '@aws-sdk/credential-provider-ini': 3.973.19 - '@aws-sdk/credential-provider-process': 3.972.74 - '@aws-sdk/credential-provider-sso': 3.973.18 - '@aws-sdk/credential-provider-web-identity': 3.972.80 + '@aws-sdk/credential-provider-env': 3.972.75 + '@aws-sdk/credential-provider-http': 3.972.77 + '@aws-sdk/credential-provider-ini': 3.973.20 + '@aws-sdk/credential-provider-process': 3.972.75 + '@aws-sdk/credential-provider-sso': 3.973.19 + '@aws-sdk/credential-provider-web-identity': 3.972.81 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.74': + '@aws-sdk/credential-provider-process@3.972.75': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.18': + '@aws-sdk/credential-provider-sso@3.973.19': dependencies: - '@aws-sdk/core': 3.978.3 - '@aws-sdk/nested-clients': 3.997.48 - '@aws-sdk/token-providers': 3.1150.0 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/nested-clients': 3.997.49 + '@aws-sdk/token-providers': 3.1153.0 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.80': + '@aws-sdk/credential-provider-web-identity@3.972.81': dependencies: - '@aws-sdk/core': 3.978.3 - '@aws-sdk/nested-clients': 3.997.48 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/nested-clients': 3.997.49 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/lib-storage@3.1150.0(@aws-sdk/client-s3@3.1150.0)': + '@aws-sdk/lib-storage@3.1152.0(@aws-sdk/client-s3@3.1152.0)': dependencies: - '@aws-sdk/client-s3': 3.1150.0 + '@aws-sdk/client-s3': 3.1152.0 '@smithy/core': 3.35.2 '@smithy/types': 4.19.0 buffer: 5.6.0 @@ -7355,21 +7431,21 @@ snapshots: stream-browserify: 3.0.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.79': + '@aws-sdk/middleware-sdk-s3@3.972.80': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/signature-v4-multi-region': 3.996.48 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.48': + '@aws-sdk/nested-clients@3.997.49': dependencies: - '@aws-sdk/core': 3.978.3 + '@aws-sdk/core': 3.978.4 '@aws-sdk/signature-v4-multi-region': 3.996.48 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -7382,12 +7458,12 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1150.0': + '@aws-sdk/token-providers@3.1153.0': dependencies: - '@aws-sdk/core': 3.978.3 - '@aws-sdk/nested-clients': 3.997.48 + '@aws-sdk/core': 3.978.4 + '@aws-sdk/nested-clients': 3.997.49 '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -7403,10 +7479,10 @@ snapshots: '@aws/lambda-invoke-store@0.3.0': {} - '@axe-core/playwright@4.13.0(playwright-core@1.64.0)': + '@axe-core/playwright@4.14.0(playwright-core@1.64.1)': dependencies: - axe-core: 4.13.0 - playwright-core: 1.64.0 + axe-core: 4.14.0 + playwright-core: 1.64.1 '@babel/code-frame@7.29.7': dependencies: @@ -7831,29 +7907,29 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@dnd-kit/accessibility@3.1.1(react@19.3.0)': + '@dnd-kit/accessibility@3.1.1(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 tslib: 2.8.1 - '@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.3.0) - '@dnd-kit/utilities': 3.2.2(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@dnd-kit/accessibility': 3.1.1(react@19.3.1) + '@dnd-kit/utilities': 3.2.2(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) tslib: 2.8.1 - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0)': + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react@19.3.1)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@dnd-kit/utilities': 3.2.2(react@19.3.0) - react: 19.3.0 + '@dnd-kit/core': 6.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@dnd-kit/utilities': 3.2.2(react@19.3.1) + react: 19.3.1 tslib: 2.8.1 - '@dnd-kit/utilities@3.2.2(react@19.3.0)': + '@dnd-kit/utilities@3.2.2(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 tslib: 2.8.1 '@dotenvx/dotenvx@1.75.1': @@ -7893,12 +7969,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.11.2': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -7910,11 +7980,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 @@ -7995,11 +8060,11 @@ snapshots: '@floating-ui/core': 1.8.0 '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@floating-ui/react-dom@2.1.9(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@floating-ui/dom': 1.8.0 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) '@floating-ui/utils@0.2.12': {} @@ -8313,30 +8378,6 @@ snapshots: '@levischuck/tiny-cbor@0.2.11': {} - '@modelcontextprotocol/sdk@1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@3.25.76)': - dependencies: - '@hono/node-server': 1.19.17(hono@4.13.13) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1(supports-color@7.2.0) - express-rate-limit: 8.6.2(express@5.2.1(supports-color@7.2.0))(supports-color@7.2.0) - hono: 4.13.13 - jose: 6.2.12 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - optionalDependencies: - '@cfworker/json-schema': 4.1.1 - transitivePeerDependencies: - - supports-color - '@modelcontextprotocol/sdk@1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@4.6.7)': dependencies: '@hono/node-server': 1.19.17(hono@4.13.13) @@ -8468,13 +8509,6 @@ snapshots: '@tybys/wasm-util': 0.10.4 optional: true - '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.4 - optional: true - '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: '@emnapi/core': 1.11.2 @@ -8667,10 +8701,10 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.153.0': optional: true - '@oxc-project/types@0.139.0': {} - '@oxc-project/types@0.153.0': {} + '@oxc-project/types@0.154.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true @@ -8860,31 +8894,31 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.64.0': + '@playwright/test@1.64.1': dependencies: - playwright: 1.64.0 + playwright: 1.64.1 '@polka/url@1.0.0-next.29': {} - '@prisma/adapter-pg@7.10.0': + '@prisma/adapter-pg@7.10.1': dependencies: - '@prisma/driver-adapter-utils': 7.10.0 + '@prisma/driver-adapter-utils': 7.10.1 '@types/pg': 8.23.1 pg: 8.23.1 postgres-array: 3.0.4 transitivePeerDependencies: - pg-native - '@prisma/client-runtime-utils@7.10.0': {} + '@prisma/client-runtime-utils@7.10.1': {} - '@prisma/client@7.10.0(prisma@7.10.0(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@6.0.3))(typescript@6.0.3)': + '@prisma/client@7.10.1(prisma@7.10.1(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3))(typescript@6.0.3)': dependencies: - '@prisma/client-runtime-utils': 7.10.0 + '@prisma/client-runtime-utils': 7.10.1 optionalDependencies: - prisma: 7.10.0(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@6.0.3) + prisma: 7.10.1(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3) typescript: 6.0.3 - '@prisma/config@7.10.0(magicast@0.5.5)': + '@prisma/config@7.10.1(magicast@0.5.5)': dependencies: c12: 3.3.4(magicast@0.5.5) deepmerge-ts: 8.0.2 @@ -8893,7 +8927,7 @@ snapshots: transitivePeerDependencies: - magicast - '@prisma/debug@7.10.0': {} + '@prisma/debug@7.10.1': {} '@prisma/debug@7.2.0': {} @@ -8917,28 +8951,28 @@ snapshots: transitivePeerDependencies: - typescript - '@prisma/driver-adapter-utils@7.10.0': + '@prisma/driver-adapter-utils@7.10.1': dependencies: - '@prisma/debug': 7.10.0 + '@prisma/debug': 7.10.1 - '@prisma/engines-version@7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3': {} + '@prisma/engines-version@8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8': {} - '@prisma/engines@7.10.0': + '@prisma/engines@7.10.1': dependencies: - '@prisma/debug': 7.10.0 - '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 - '@prisma/fetch-engine': 7.10.0 - '@prisma/get-platform': 7.10.0 + '@prisma/debug': 7.10.1 + '@prisma/engines-version': 8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8 + '@prisma/fetch-engine': 7.10.1 + '@prisma/get-platform': 7.10.1 - '@prisma/fetch-engine@7.10.0': + '@prisma/fetch-engine@7.10.1': dependencies: - '@prisma/debug': 7.10.0 - '@prisma/engines-version': 7.10.0-4.0edf323efd1d98336f3f0a68684b56f689b900d3 - '@prisma/get-platform': 7.10.0 + '@prisma/debug': 7.10.1 + '@prisma/engines-version': 8.1.0-1.d1b3f377ca835d73fd50954a5e22128b9ebdd6b8 + '@prisma/get-platform': 7.10.1 - '@prisma/get-platform@7.10.0': + '@prisma/get-platform@7.10.1': dependencies: - '@prisma/debug': 7.10.0 + '@prisma/debug': 7.10.1 '@prisma/get-platform@7.2.0': dependencies: @@ -8953,22 +8987,22 @@ snapshots: env-paths: 3.0.0 proper-lockfile: 4.1.2 - '@prisma/studio-core@0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@prisma/studio-core@0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) '@types/react': 19.3.0 '@visx/curve': 4.0.1-alpha.0 '@visx/event': 4.0.1-alpha.0 - '@visx/grid': 4.0.1-alpha.0(react@19.3.0) - '@visx/group': 4.0.1-alpha.0(react@19.3.0) - '@visx/responsive': 4.0.1-alpha.0(react@19.3.0) + '@visx/grid': 4.0.1-alpha.0(react@19.3.1) + '@visx/group': 4.0.1-alpha.0(react@19.3.1) + '@visx/responsive': 4.0.1-alpha.0(react@19.3.1) '@visx/scale': 4.0.1-alpha.0 - '@visx/shape': 4.0.1-alpha.0(react@19.3.0) + '@visx/shape': 4.0.1-alpha.0(react@19.3.1) d3-array: 3.2.4 d3-shape: 3.2.0 elkjs: 0.11.1 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) transitivePeerDependencies: - '@types/react-dom' @@ -8998,809 +9032,809 @@ snapshots: '@radix-ui/primitive@1.2.0': {} - '@radix-ui/react-accessible-icon@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-accessible-icon@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-accordion@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-accordion@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-alert-dialog@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-alert-dialog@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-arrow@1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-arrow@1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-aspect-ratio@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-aspect-ratio@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-avatar@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-avatar@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-checkbox@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-checkbox@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-collapsible@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-collapsible@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-collection@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-collection@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-compose-refs@1.1.5(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-context-menu@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-context-menu@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-context@1.3.0(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-context@1.3.0(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-dialog@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-dialog@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) aria-hidden: 1.2.6 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-direction@1.2.0(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-direction@1.2.0(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-dismissable-layer@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-dismissable-layer@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-dropdown-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-dropdown-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-focus-guards@1.2.0(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-focus-guards@1.2.0(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-focus-scope@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-focus-scope@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-form@0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-form@0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-hover-card@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-hover-card@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-id@1.1.4(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-id@1.1.4(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-label@2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-label@2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) aria-hidden: 1.2.6 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-menubar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-menubar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-navigation-menu@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-navigation-menu@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-one-time-password-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-one-time-password-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-password-toggle-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-password-toggle-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-popover@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-popover@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) aria-hidden: 1.2.6 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-popper@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-popper@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-rect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) '@radix-ui/rect': 1.1.3 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-portal@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-portal@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-presence@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-presence@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-primitive@2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-primitive@2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-progress@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-progress@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-radio-group@1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-radio-group@1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-roving-focus@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-roving-focus@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-scroll-area@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-scroll-area@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-select@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-select@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) aria-hidden: 1.2.6 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-separator@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-separator@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-slider@1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-slider@1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-slot@1.2.3(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-slot@1.2.3(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-slot@1.5.0(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-slot@1.5.0(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-switch@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-switch@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-tabs@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-tabs@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toast@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-toast@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toggle-group@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-toggle-group@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toggle@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-toggle@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toolbar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-toolbar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-tooltip@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-tooltip@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-use-callback-ref@1.1.5(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-callback-ref@1.1.5(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-controllable-state@1.3.0(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-controllable-state@1.3.0(@types/react@19.3.0)(react@19.3.1)': dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-escape-keydown@1.1.6(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-escape-keydown@1.1.6(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-previous@1.1.4(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-previous@1.1.4(@types/react@19.3.0)(react@19.3.1)': dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-rect@1.1.4(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-rect@1.1.4(@types/react@19.3.0)(react@19.3.1)': dependencies: '@radix-ui/rect': 1.1.3 - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-use-size@1.1.5(@types/react@19.3.0)(react@19.3.0)': + '@radix-ui/react-use-size@1.1.5(@types/react@19.3.0)(react@19.3.1)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - react: 19.3.0 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - '@radix-ui/react-visually-hidden@1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@radix-ui/react-visually-hidden@1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) '@radix-ui/rect@1.1.3': {} - '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1))(react@19.3.0)': + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1))(react@19.3.1)': dependencies: '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 @@ -9809,56 +9843,52 @@ snapshots: redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 optionalDependencies: - react: 19.3.0 - react-redux: 9.2.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1) + react: 19.3.1 + react-redux: 9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1) - '@rolldown/binding-android-arm64@1.1.5': + '@rolldown/binding-android-arm-eabi@1.2.14': optional: true - '@rolldown/binding-darwin-arm64@1.1.5': + '@rolldown/binding-android-arm64@1.2.14': optional: true - '@rolldown/binding-darwin-x64@1.1.5': + '@rolldown/binding-darwin-arm64@1.2.14': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@rolldown/binding-darwin-x64@1.2.14': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@rolldown/binding-freebsd-x64@1.2.14': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@rolldown/binding-linux-arm64-gnu@1.2.14': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@rolldown/binding-linux-arm64-musl@1.2.14': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.14': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@rolldown/binding-linux-s390x-gnu@1.2.14': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@rolldown/binding-linux-x64-gnu@1.2.14': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': + '@rolldown/binding-linux-x64-musl@1.2.14': optional: true - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.2.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@rolldown/binding-openharmony-arm64@1.2.14': optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': + '@rolldown/binding-win32-arm64-msvc@1.2.14': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@rolldown/binding-win32-x64-msvc@1.2.14': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -9867,7 +9897,7 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@shadcn/registry@0.1.3(supports-color@7.2.0)': + '@shadcn/registry@0.2.0(supports-color@7.2.0)': dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/parser': 7.29.9 @@ -9891,7 +9921,7 @@ snapshots: tsconfig-paths: 4.2.0 type-fest: 4.41.0 undici: 7.30.0 - zod: 3.25.76 + zod: 4.6.7 transitivePeerDependencies: - supports-color @@ -9917,27 +9947,32 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@smithy/core@3.36.1': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.5.2': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/fetch-http-handler@5.8.0': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/node-http-handler@4.12.1': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/signature-v4@5.7.4': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.36.1 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -10006,14 +10041,14 @@ snapshots: '@stryker-mutator/util@10.0.0': {} - '@stryker-mutator/vitest-runner@10.0.0(patch_hash=9ad3e9855fd9781fd36e872ed42c5f934f91b962d6830e6cc56d56bf16e1dabd)(@stryker-mutator/core@10.0.0(@types/node@26.6.5))(vitest@5.0.4)': + '@stryker-mutator/vitest-runner@10.0.0(patch_hash=9ad3e9855fd9781fd36e872ed42c5f934f91b962d6830e6cc56d56bf16e1dabd)(@stryker-mutator/core@10.0.0(@types/node@26.6.5))(vitest@5.0.5)': dependencies: '@stryker-mutator/api': 10.0.0 '@stryker-mutator/core': 10.0.0(@types/node@26.6.5) '@stryker-mutator/util': 10.0.0 semver: 7.8.5 tslib: 2.8.1 - vitest: 5.0.4(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) '@swc/helpers@0.5.23': dependencies: @@ -10088,20 +10123,20 @@ snapshots: postcss: 8.5.26 tailwindcss: 4.3.3 - '@tanstack/query-core@5.104.2': {} + '@tanstack/query-core@5.104.3': {} - '@tanstack/react-query@5.104.2(react@19.3.0)': + '@tanstack/react-query@5.104.3(react@19.3.1)': dependencies: - '@tanstack/query-core': 5.104.2 - react: 19.3.0 + '@tanstack/query-core': 5.104.3 + react: 19.3.1 - '@tanstack/react-virtual@3.14.17(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-virtual@3.14.21(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: - '@tanstack/virtual-core': 3.19.2 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@tanstack/virtual-core': 3.19.6 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) - '@tanstack/virtual-core@3.19.2': {} + '@tanstack/virtual-core@3.19.6': {} '@tesseract.js-data/deu@1.0.0': {} @@ -10450,47 +10485,47 @@ snapshots: '@types/react': 19.3.0 '@visx/point': 4.0.1-alpha.0 - '@visx/grid@4.0.1-alpha.0(react@19.3.0)': + '@visx/grid@4.0.1-alpha.0(react@19.3.1)': dependencies: '@types/react': 19.3.0 '@visx/curve': 4.0.1-alpha.0 - '@visx/group': 4.0.1-alpha.0(react@19.3.0) + '@visx/group': 4.0.1-alpha.0(react@19.3.1) '@visx/point': 4.0.1-alpha.0 '@visx/scale': 4.0.1-alpha.0 - '@visx/shape': 4.0.1-alpha.0(react@19.3.0) + '@visx/shape': 4.0.1-alpha.0(react@19.3.1) classnames: 2.5.1 - react: 19.3.0 + react: 19.3.1 - '@visx/group@4.0.1-alpha.0(react@19.3.0)': + '@visx/group@4.0.1-alpha.0(react@19.3.1)': dependencies: '@types/react': 19.3.0 classnames: 2.5.1 - react: 19.3.0 + react: 19.3.1 '@visx/point@4.0.1-alpha.0': {} - '@visx/responsive@4.0.1-alpha.0(react@19.3.0)': + '@visx/responsive@4.0.1-alpha.0(react@19.3.1)': dependencies: '@types/lodash': 4.17.25 '@types/react': 19.3.0 lodash: 4.18.1 - react: 19.3.0 + react: 19.3.1 '@visx/scale@4.0.1-alpha.0': dependencies: '@visx/vendor': 4.0.0-alpha.0 - '@visx/shape@4.0.1-alpha.0(react@19.3.0)': + '@visx/shape@4.0.1-alpha.0(react@19.3.1)': dependencies: '@types/lodash': 4.17.25 '@types/react': 19.3.0 '@visx/curve': 4.0.1-alpha.0 - '@visx/group': 4.0.1-alpha.0(react@19.3.0) + '@visx/group': 4.0.1-alpha.0(react@19.3.1) '@visx/scale': 4.0.1-alpha.0 '@visx/vendor': 4.0.0-alpha.0 classnames: 2.5.1 lodash: 4.18.1 - react: 19.3.0 + react: 19.3.1 '@visx/vendor@4.0.0-alpha.0': dependencies: @@ -10518,7 +10553,7 @@ snapshots: d3-time-format: 4.1.0 internmap: 2.0.3 - '@vitest/coverage-v8@5.0.4(vitest@5.0.4)': + '@vitest/coverage-v8@5.0.5(vitest@5.0.5)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/istanbul-lib-coverage': 1.0.2 @@ -10528,7 +10563,7 @@ snapshots: obug: 3.0.0 std-env: 4.3.0 tinyrainbow: 3.2.0 - vitest: 5.0.4(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) '@vitest/istanbul-lib-coverage@1.0.2': {} @@ -10536,17 +10571,17 @@ snapshots: dependencies: '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@5.0.4(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1))': + '@vitest/mocker@5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.4 + '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 optionalDependencies: msw: 2.14.6(@types/node@26.6.5)(typescript@6.0.3) - vite: 8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) - '@vitest/spy@5.0.4': {} + '@vitest/spy@5.0.5': {} abort-controller@3.0.0: dependencies: @@ -10755,6 +10790,8 @@ snapshots: axe-core@4.13.0: {} + axe-core@4.14.0: {} + axobject-query@4.1.0: {} b4a@1.9.0: {} @@ -11072,7 +11109,7 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.8.0 - cron-parser@5.10.1: + cron-parser@5.10.2: dependencies: luxon: 3.7.2 @@ -11282,7 +11319,7 @@ snapshots: dotenv@17.4.2: {} - dotenv@18.0.7: {} + dotenv@18.0.8: {} dunder-proto@1.0.1: dependencies: @@ -12383,7 +12420,7 @@ snapshots: ms: 2.1.3 semver: 7.8.0 - jspdf-autotable@5.0.8(jspdf@4.2.1): + jspdf-autotable@5.0.9(jspdf@4.2.1): dependencies: jspdf: 4.2.1 @@ -12504,36 +12541,69 @@ snapshots: lightningcss-android-arm64@1.32.0: optional: true + lightningcss-android-arm64@1.33.0: + optional: true + lightningcss-darwin-arm64@1.32.0: optional: true + lightningcss-darwin-arm64@1.33.0: + optional: true + lightningcss-darwin-x64@1.32.0: optional: true + lightningcss-darwin-x64@1.33.0: + optional: true + lightningcss-freebsd-x64@1.32.0: optional: true + lightningcss-freebsd-x64@1.33.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + lightningcss-linux-arm64-gnu@1.32.0: optional: true + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + lightningcss-linux-arm64-musl@1.32.0: optional: true + lightningcss-linux-arm64-musl@1.33.0: + optional: true + lightningcss-linux-x64-gnu@1.32.0: optional: true + lightningcss-linux-x64-gnu@1.33.0: + optional: true + lightningcss-linux-x64-musl@1.32.0: optional: true + lightningcss-linux-x64-musl@1.33.0: + optional: true + lightningcss-win32-arm64-msvc@1.32.0: optional: true + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + lightningcss-win32-x64-msvc@1.32.0: optional: true + lightningcss-win32-x64-msvc@1.33.0: + optional: true + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 @@ -12550,6 +12620,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -12604,9 +12690,9 @@ snapshots: lru.min@1.1.5: {} - lucide-react@1.56.0(@types/react@19.3.0)(react@19.3.0): + lucide-react@1.59.0(@types/react@19.3.0)(react@19.3.1): dependencies: - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 @@ -12755,16 +12841,16 @@ snapshots: negotiator@1.0.0: {} - next@16.3.8(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.0)(@types/node@26.6.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + next@16.3.8(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.1)(@types/node@26.6.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): dependencies: '@next/env': 16.3.8 '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.11.26 caniuse-lite: 1.0.30001814 postcss: 8.5.23 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) - styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.3.1) optionalDependencies: '@next/swc-darwin-arm64': 16.3.8 '@next/swc-darwin-x64': 16.3.8 @@ -12775,7 +12861,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.3.8 '@next/swc-win32-x64-msvc': 16.3.8 '@opentelemetry/api': 1.9.1 - '@playwright/test': 1.64.0 + '@playwright/test': 1.64.1 sharp: 0.35.5(@types/node@26.6.5) transitivePeerDependencies: - '@babel/core' @@ -12797,7 +12883,7 @@ snapshots: node-releases@2.0.58: {} - nodemailer@10.1.0: {} + nodemailer@10.1.2: {} non-error@0.1.0: {} @@ -13058,10 +13144,10 @@ snapshots: performance-now@2.1.0: optional: true - pg-boss@12.37.1(@opentelemetry/api@1.9.1): + pg-boss@12.38.0(@opentelemetry/api@1.9.1): dependencies: '@opentelemetry/api': 1.9.1 - cron-parser: 5.10.1 + cron-parser: 5.10.2 pg: 8.23.1 rrule-temporal: 2.2.8 serialize-error: 13.0.2 @@ -13121,11 +13207,11 @@ snapshots: dependencies: find-up: 3.0.0 - playwright-core@1.64.0: {} + playwright-core@1.64.1: {} - playwright@1.64.0: + playwright@1.64.1: dependencies: - playwright-core: 1.64.0 + playwright-core: 1.64.1 pngjs@5.0.0: {} @@ -13182,12 +13268,12 @@ snapshots: dependencies: parse-ms: 4.0.0 - prisma@7.10.0(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@6.0.3): + prisma@7.10.1(@types/node@26.6.5)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(magicast@0.5.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(typescript@6.0.3): dependencies: - '@prisma/config': 7.10.0(magicast@0.5.5) + '@prisma/config': 7.10.1(magicast@0.5.5) '@prisma/dev': 0.24.17(typescript@6.0.3) - '@prisma/engines': 7.10.0 - '@prisma/studio-core': 0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@prisma/engines': 7.10.1 + '@prisma/studio-core': 0.33.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) mysql2: 3.24.4(@types/node@26.6.5) postgres: 3.4.7 optionalDependencies: @@ -13279,65 +13365,65 @@ snapshots: queue-microtask@1.2.3: {} - radix-ui@1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + radix-ui@1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): dependencies: '@radix-ui/primitive': 1.2.0 - '@radix-ui/react-accessible-icon': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-accordion': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-alert-dialog': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-aspect-ratio': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-avatar': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-checkbox': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-context-menu': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-dropdown-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-form': 0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-hover-card': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-menubar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-navigation-menu': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-one-time-password-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-password-toggle-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-popover': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-progress': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-radio-group': 1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-scroll-area': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-select': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slider': 1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-switch': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-tabs': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toast': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-toolbar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-tooltip': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-escape-keydown': 1.1.6(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.0) - '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + '@radix-ui/react-accessible-icon': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-accordion': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-alert-dialog': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-aspect-ratio': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-avatar': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-checkbox': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context-menu': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-dropdown-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-form': 0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-hover-card': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-menubar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-navigation-menu': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-one-time-password-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-password-toggle-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popover': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-progress': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-radio-group': 1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-scroll-area': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-select': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slider': 1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-switch': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-tabs': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toast': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toolbar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-tooltip': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.6(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 '@types/react-dom': 19.3.0(@types/react@19.3.0) @@ -13361,58 +13447,58 @@ snapshots: defu: 6.1.7 destr: 2.0.5 - react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0): + react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.1): dependencies: '@date-fns/tz': 1.5.0 date-fns: 4.4.0 - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@types/react': 19.3.0 - react-dom@19.3.0(react@19.3.0): + react-dom@19.3.1(react@19.3.1): dependencies: - react: 19.3.0 + react: 19.3.1 scheduler: 0.28.0 react-is@16.13.1: {} - react-redux@9.2.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.3.0 - use-sync-external-store: 1.6.0(react@19.3.0) + react: 19.3.1 + use-sync-external-store: 1.6.0(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 redux: 5.0.1 - react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.3.0): + react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.3.1): dependencies: - react: 19.3.0 - react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.1) tslib: 2.8.1 optionalDependencies: '@types/react': 19.3.0 - react-remove-scroll@2.7.2(@types/react@19.3.0)(react@19.3.0): + react-remove-scroll@2.7.2(@types/react@19.3.0)(react@19.3.1): dependencies: - react: 19.3.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.3.0)(react@19.3.0) - react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.3.0)(react@19.3.1) + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.1) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.3.0)(react@19.3.0) - use-sidecar: 1.1.3(@types/react@19.3.0)(react@19.3.0) + use-callback-ref: 1.3.3(@types/react@19.3.0)(react@19.3.1) + use-sidecar: 1.1.3(@types/react@19.3.0)(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 - react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.3.0): + react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.3.1): dependencies: get-nonce: 1.0.1 - react: 19.3.0 + react: 19.3.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.3.0 - react@19.3.0: {} + react@19.3.1: {} readable-stream@2.3.8: dependencies: @@ -13452,21 +13538,21 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 - recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react-is@16.13.1)(react@19.3.0)(redux@5.0.1): + recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@16.13.1)(react@19.3.1)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1))(react@19.3.0) + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1))(react@19.3.1) clsx: 2.1.1 decimal.js-light: 2.5.1 es-toolkit: 1.45.1 eventemitter3: 5.0.4 immer: 11.1.15 - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) react-is: 16.13.1 - react-redux: 9.2.0(@types/react@19.3.0)(react@19.3.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1) reselect: 5.2.0 tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@19.3.0) + use-sync-external-store: 1.6.0(react@19.3.1) victory-vendor: 37.3.6 transitivePeerDependencies: - '@types/react' @@ -13546,26 +13632,26 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.1.5: + rolldown@1.2.14: dependencies: - '@oxc-project/types': 0.139.0 + '@oxc-project/types': 0.154.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 + '@rolldown/binding-android-arm-eabi': 1.2.14 + '@rolldown/binding-android-arm64': 1.2.14 + '@rolldown/binding-darwin-arm64': 1.2.14 + '@rolldown/binding-darwin-x64': 1.2.14 + '@rolldown/binding-freebsd-x64': 1.2.14 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.14 + '@rolldown/binding-linux-arm64-gnu': 1.2.14 + '@rolldown/binding-linux-arm64-musl': 1.2.14 + '@rolldown/binding-linux-ppc64-gnu': 1.2.14 + '@rolldown/binding-linux-s390x-gnu': 1.2.14 + '@rolldown/binding-linux-x64-gnu': 1.2.14 + '@rolldown/binding-linux-x64-musl': 1.2.14 + '@rolldown/binding-openharmony-arm64': 1.2.14 + '@rolldown/binding-win32-arm64-msvc': 1.2.14 + '@rolldown/binding-win32-x64-msvc': 1.2.14 router@2.2.0(supports-color@7.2.0): dependencies: @@ -13693,15 +13779,15 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.21.4(@cfworker/json-schema@4.1.1)(supports-color@7.2.0): + shadcn@4.22.0(@cfworker/json-schema@4.1.1)(supports-color@7.2.0): dependencies: '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/parser': 7.29.9 '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@dotenvx/dotenvx': 1.75.1 - '@modelcontextprotocol/sdk': 1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@3.25.76) - '@shadcn/registry': 0.1.3(supports-color@7.2.0) + '@modelcontextprotocol/sdk': 1.32.1(@cfworker/json-schema@4.1.1)(supports-color@7.2.0)(zod@4.6.7) + '@shadcn/registry': 0.2.0(supports-color@7.2.0) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.29.3 cn: 0.2.6 @@ -13726,8 +13812,7 @@ snapshots: tsconfig-paths: 4.2.0 undici: 7.30.0 validate-npm-package-name: 7.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) + zod: 4.6.7 transitivePeerDependencies: - '@cfworker/json-schema' - babel-plugin-macros @@ -13822,10 +13907,10 @@ snapshots: ip-address: 10.7.3 smart-buffer: 4.2.0 - sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): + sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): dependencies: - react: 19.3.0 - react-dom: 19.3.0(react@19.3.0) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) optionalDependencies: '@types/react': 19.3.0 @@ -13990,10 +14075,10 @@ snapshots: strip-json-comments@5.0.3: {} - styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.3.0): + styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@7.2.0))(react@19.3.1): dependencies: client-only: 0.0.1 - react: 19.3.0 + react: 19.3.1 optionalDependencies: '@babel/core': 7.29.7(supports-color@7.2.0) @@ -14095,7 +14180,7 @@ snapshots: ssh-remote-port-forward: 1.0.4 tar-fs: 3.1.3 tmp: 0.2.7 - undici: 8.11.2 + undici: 8.12.3 transitivePeerDependencies: - bare-abort-controller - bare-buffer @@ -14277,7 +14362,7 @@ snapshots: undici@7.30.0: {} - undici@8.11.2: {} + undici@8.12.3: {} unicorn-magic@0.3.0: {} @@ -14325,24 +14410,24 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.0): + use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.1): dependencies: - react: 19.3.0 + react: 19.3.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.3.0 - use-sidecar@1.1.3(@types/react@19.3.0)(react@19.3.0): + use-sidecar@1.1.3(@types/react@19.3.0)(react@19.3.1): dependencies: detect-node-es: 1.1.0 - react: 19.3.0 + react: 19.3.1 tslib: 2.8.1 optionalDependencies: '@types/react': 19.3.0 - use-sync-external-store@1.6.0(react@19.3.0): + use-sync-external-store@1.6.0(react@19.3.1): dependencies: - react: 19.3.0 + react: 19.3.1 util-deprecate@1.0.2: {} @@ -14384,12 +14469,12 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1): + vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1): dependencies: - lightningcss: 1.32.0 + lightningcss: 1.33.0 picomatch: 4.0.7 postcss: 8.5.29 - rolldown: 1.1.5 + rolldown: 1.2.14 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.6.5 @@ -14397,10 +14482,10 @@ snapshots: jiti: 2.7.0 yaml: 2.9.1 - vitest@5.0.4(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.4)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.4(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + '@vitest/mocker': 5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -14411,12 +14496,12 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 8.1.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 26.6.5 - '@vitest/coverage-v8': 5.0.4(vitest@5.0.4) + '@vitest/coverage-v8': 5.0.5(vitest@5.0.5) transitivePeerDependencies: - msw @@ -14612,10 +14697,6 @@ snapshots: dependencies: zod: 4.6.7 - zod-to-json-schema@3.25.2(zod@3.25.76): - dependencies: - zod: 3.25.76 - zod-to-json-schema@3.25.2(zod@4.6.7): dependencies: zod: 4.6.7 @@ -14624,8 +14705,6 @@ snapshots: dependencies: zod: 4.6.7 - zod@3.25.76: {} - zod@4.6.7: {} zxcvbn-typescript@5.0.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5f13e535e..5d93cae2c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -103,6 +103,13 @@ overrides: "brace-expansion@>=4.0.0 <5.0.12": "^5.0.12" "undici@>=7.0.0 <7.29.1": "^7.29.1" + # GHSA-587h-7wmj-h5pm (HIGH), GHSA-rq7h-c2jc-7f22, GHSA-r825-w8mm-5pxw, + # GHSA-698v-4r3x-3vcv and GHSA-9jrq-w75r-8gcw: dev-server file serving, + # cross-origin middleware and the bundled launch-editor. Vite arrives only + # under vitest, never in the image; vitest asks for ^8, so a floor on the + # same major moves it and nothing else. + "vite@>=8.0.0 <8.1.6": "^8.1.6" + allowBuilds: "@prisma/engines": true prisma: true diff --git a/src/lib/jobs/__tests__/job-failures-retention.test.ts b/src/lib/jobs/__tests__/job-failures-retention.test.ts index 1e03effe1..6219eb253 100644 --- a/src/lib/jobs/__tests__/job-failures-retention.test.ts +++ b/src/lib/jobs/__tests__/job-failures-retention.test.ts @@ -18,7 +18,7 @@ describe("pg-boss failed-row ledger retention", () => { ) as { version: string }; const plans = readFileSync(join(PG_BOSS_ROOT, "dist/plans.js"), "utf8"); - expect(packageJson.version).toBe("12.37.1"); + expect(packageJson.version).toBe("12.38.0"); expect(plans).toContain("retention_seconds: FORTEEN_DAYS"); expect(plans).toContain("deletion_seconds: SEVEN_DAYS"); // 12.37 reads the clock through job_now(), which is pg_catalog.now() From 01c935261ba6f9adf9bebdb77d6f23f844d19e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 06:53:04 +0200 Subject: [PATCH 02/14] chore(deps): take nodemailer 10.1.7 for the address parser advisories GHSA-8c8m-p3m8-cjg8 and two sibling advisories against the address parser are fixed in 10.1.4; 10.1.5 to 10.1.7 are linear-time and encoding fixes on the same line. The email channel hands the parser a recipient address each account sets for itself. --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2f3026328..06671fd29 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "lucide-react": "^1.59.0", "mmdb-lib": "^3.0.3", "next": "16.3.8", - "nodemailer": "10.1.2", + "nodemailer": "10.1.7", "otpauth": "^9.5.2", "p-limit": "^7.3.3", "pg": "^8.23.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d648306e..6cee5a51f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,8 +127,8 @@ importers: specifier: 16.3.8 version: 16.3.8(@babel/core@7.29.7(supports-color@7.2.0))(@opentelemetry/api@1.9.1)(@playwright/test@1.64.1)(@types/node@26.6.5)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) nodemailer: - specifier: 10.1.2 - version: 10.1.2 + specifier: 10.1.7 + version: 10.1.7 otpauth: specifier: ^9.5.2 version: 9.5.2 @@ -5698,8 +5698,8 @@ packages: resolution: {integrity: sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==} engines: {node: '>=18'} - nodemailer@10.1.2: - resolution: {integrity: sha512-rnQn9Rzh8RAwcp42MVsIvxbsv6YZ2a2/av+uyJ/LPU1+2QSlrRgFsWWZTKenyRUqkIl8urOI0c4+CEcmFdsvQw==} + nodemailer@10.1.7: + resolution: {integrity: sha512-OQa6FDln5fLttmqSbjDiRYvFDOFNrBqfiOwHPlEbURil3b+vPB1v8xpi+Sp46drzNDlsTnNsS502vthmz56r4w==} engines: {node: '>=20.0.0'} non-error@0.1.0: @@ -12883,7 +12883,7 @@ snapshots: node-releases@2.0.58: {} - nodemailer@10.1.2: {} + nodemailer@10.1.7: {} non-error@0.1.0: {} From 96062d198a894eae27c20a6c93d275d6efbbc0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 06:58:12 +0200 Subject: [PATCH 03/14] chore(deps): keep vite on the 8.1 line its advisories were fixed on Re-resolving Vitest 5.0.5 had carried vite from 8.1.5 to 8.3.4, two minors nobody asked for: 8.2 resolves the root through real paths, which moves file ids in a symlinked worktree, and 8.3 warns on every run about config-loader features the test configs use. 8.1.6 carries the same five fixes as a backport, so the lockfile takes that and the floor in the workspace file stays as it is. --- pnpm-lock.yaml | 199 ++++++++++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 86 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6cee5a51f..579276b80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -291,7 +291,7 @@ importers: version: 6.0.3 vitest: specifier: ^5.0.5 - version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + version: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) packages: @@ -764,12 +764,18 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} @@ -1689,12 +1695,12 @@ packages: cpu: [x64] os: [win32] + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/types@0.153.0': resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} - '@oxc-project/types@0.154.0': - resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} - '@oxc-resolver/binding-android-arm-eabi@11.24.2': resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] @@ -2738,98 +2744,97 @@ packages: react-redux: optional: true - '@rolldown/binding-android-arm-eabi@1.2.14': - resolution: {integrity: sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@rolldown/binding-android-arm64@1.2.14': - resolution: {integrity: sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==} + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.14': - resolution: {integrity: sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==} + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.14': - resolution: {integrity: sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==} + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.14': - resolution: {integrity: sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==} + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.14': - resolution: {integrity: sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.14': - resolution: {integrity: sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==} + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.14': - resolution: {integrity: sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==} + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.14': - resolution: {integrity: sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==} + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.14': - resolution: {integrity: sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==} + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.14': - resolution: {integrity: sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==} + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.14': - resolution: {integrity: sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==} + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.14': - resolution: {integrity: sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==} + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.14': - resolution: {integrity: sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==} + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.14': - resolution: {integrity: sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==} + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -6391,8 +6396,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.2.14: - resolution: {integrity: sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==} + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -7037,13 +7042,13 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite@8.3.4: - resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} + vite@8.1.6: + resolution: {integrity: sha512-aRAHIxx5SW5zc1sMqFwJU/t5qaFfXb5Hj0J7W0VXihu/X99+346tz6080tO1T1xVIJORX/b3zVTXGFqUXh35uw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.7.1 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.28.1 jiti: '>=1.21.0' less: ^4.0.0 @@ -7969,6 +7974,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.11.2': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -7980,6 +7991,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 @@ -8509,6 +8525,13 @@ snapshots: '@tybys/wasm-util': 0.10.4 optional: true + '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.4 + optional: true + '@napi-rs/wasm-runtime@1.2.5(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: '@emnapi/core': 1.11.2 @@ -8701,9 +8724,9 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.153.0': optional: true - '@oxc-project/types@0.153.0': {} + '@oxc-project/types@0.139.0': {} - '@oxc-project/types@0.154.0': {} + '@oxc-project/types@0.153.0': {} '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true @@ -9846,49 +9869,53 @@ snapshots: react: 19.3.1 react-redux: 9.2.0(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1) - '@rolldown/binding-android-arm-eabi@1.2.14': + '@rolldown/binding-android-arm64@1.1.5': optional: true - '@rolldown/binding-android-arm64@1.2.14': + '@rolldown/binding-darwin-arm64@1.1.5': optional: true - '@rolldown/binding-darwin-arm64@1.2.14': + '@rolldown/binding-darwin-x64@1.1.5': optional: true - '@rolldown/binding-darwin-x64@1.2.14': + '@rolldown/binding-freebsd-x64@1.1.5': optional: true - '@rolldown/binding-freebsd-x64@1.2.14': + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.14': + '@rolldown/binding-linux-arm64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.14': + '@rolldown/binding-linux-arm64-musl@1.1.5': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.14': + '@rolldown/binding-linux-ppc64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.14': + '@rolldown/binding-linux-s390x-gnu@1.1.5': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.14': + '@rolldown/binding-linux-x64-gnu@1.1.5': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.14': + '@rolldown/binding-linux-x64-musl@1.1.5': optional: true - '@rolldown/binding-linux-x64-musl@1.2.14': + '@rolldown/binding-openharmony-arm64@1.1.5': optional: true - '@rolldown/binding-openharmony-arm64@1.2.14': + '@rolldown/binding-wasm32-wasi@1.1.5': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.2.5(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.14': + '@rolldown/binding-win32-arm64-msvc@1.1.5': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.14': + '@rolldown/binding-win32-x64-msvc@1.1.5': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -10048,7 +10075,7 @@ snapshots: '@stryker-mutator/util': 10.0.0 semver: 7.8.5 tslib: 2.8.1 - vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) '@swc/helpers@0.5.23': dependencies: @@ -10563,7 +10590,7 @@ snapshots: obug: 3.0.0 std-env: 4.3.0 tinyrainbow: 3.2.0 - vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + vitest: 5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) '@vitest/istanbul-lib-coverage@1.0.2': {} @@ -10571,7 +10598,7 @@ snapshots: dependencies: '@vitest/istanbul-lib-coverage': 1.0.2 - '@vitest/mocker@5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1))': + '@vitest/mocker@5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 @@ -10579,7 +10606,7 @@ snapshots: magic-string: 1.4.3 optionalDependencies: msw: 2.14.6(@types/node@26.6.5)(typescript@6.0.3) - vite: 8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) '@vitest/spy@5.0.5': {} @@ -13632,26 +13659,26 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.2.14: + rolldown@1.1.5: dependencies: - '@oxc-project/types': 0.154.0 + '@oxc-project/types': 0.139.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.14 - '@rolldown/binding-android-arm64': 1.2.14 - '@rolldown/binding-darwin-arm64': 1.2.14 - '@rolldown/binding-darwin-x64': 1.2.14 - '@rolldown/binding-freebsd-x64': 1.2.14 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.14 - '@rolldown/binding-linux-arm64-gnu': 1.2.14 - '@rolldown/binding-linux-arm64-musl': 1.2.14 - '@rolldown/binding-linux-ppc64-gnu': 1.2.14 - '@rolldown/binding-linux-s390x-gnu': 1.2.14 - '@rolldown/binding-linux-x64-gnu': 1.2.14 - '@rolldown/binding-linux-x64-musl': 1.2.14 - '@rolldown/binding-openharmony-arm64': 1.2.14 - '@rolldown/binding-win32-arm64-msvc': 1.2.14 - '@rolldown/binding-win32-x64-msvc': 1.2.14 + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 router@2.2.0(supports-color@7.2.0): dependencies: @@ -14469,12 +14496,12 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1): + vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 postcss: 8.5.29 - rolldown: 1.2.14 + rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.6.5 @@ -14482,10 +14509,10 @@ snapshots: jiti: 2.7.0 yaml: 2.9.1 - vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)): + vitest@5.0.5(@opentelemetry/api@1.9.1)(@types/node@26.6.5)(@vitest/coverage-v8@5.0.5)(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) + '@vitest/mocker': 5.0.5(msw@2.14.6(@types/node@26.6.5)(typescript@6.0.3))(vite@8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1)) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -14496,7 +14523,7 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 8.3.4(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.1.6(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.1) why-is-node-running: 3.2.1 optionalDependencies: '@opentelemetry/api': 1.9.1 From 4f2af7c544235cec1c3ede12825b9937b41a7ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:06:51 +0200 Subject: [PATCH 04/14] test(release): hold the image's Prisma CLI install to the app's versions The guard pinned the npm-installed dotenv in the Dockerfile to a literal, which is how that install came to trail the app tree by a patch without anything noticing. It now reads the Prisma and dotenv versions from package.json and asks the Dockerfile for the same ones, engines included. --- scripts/__tests__/release-workflows.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/__tests__/release-workflows.test.ts b/scripts/__tests__/release-workflows.test.ts index 79e27408a..c402102b8 100644 --- a/scripts/__tests__/release-workflows.test.ts +++ b/scripts/__tests__/release-workflows.test.ts @@ -236,6 +236,7 @@ describe("release container inputs", () => { const dockerfile = readRepoFile("Dockerfile"); const packageJson = JSON.parse(readRepoFile("package.json")) as { packageManager: string; + dependencies: Record; }; expect(packageJson.packageManager).toBe("pnpm@11.15.1"); expect(dockerfile).not.toContain("pnpm@latest"); @@ -249,7 +250,15 @@ describe("release container inputs", () => { expect(dockerfile).toContain( "ln -sfn /opt/prisma-cli/node_modules/.bin/tsx /usr/local/bin/healthlog-tsx", ); - expect(dockerfile).toContain("dotenv@18.0.6"); + // The image's npm-installed Prisma CLI, its engines and dotenv are a second + // install of packages the app tree already declares; they move together or + // the image runs a CLI the tests never did. Drift is how the dotenv pin + // here trailed the app's by a patch. + const floor = (name: string) => + packageJson.dependencies[name]!.replace(/^[\^~]/, ""); + expect(dockerfile).toContain(`prisma@${floor("prisma")} `); + expect(dockerfile).toContain(`@prisma/engines@${floor("prisma")} `); + expect(dockerfile).toContain(`dotenv@${floor("dotenv")} `); expect(dockerfile).toContain( "ln -sfn /opt/prisma-cli/node_modules/dotenv /app/node_modules/dotenv", ); From c272324c1e26ff8b8eaf5a5078415cfa0ee818cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:07:19 +0200 Subject: [PATCH 05/14] fix(api): declare the auth gate's 403 on every operation behind it requireAuth answers 403 auth.scope.insufficient to a narrow Bearer token on a route that names no scope it holds, and requireAdmin answers 403 auth.admin.required to a session that is not an administrator's. Both run before any route body, and the contract only said so in the prose of the shared 401, so a client generated from it had no typed branch for either. The route kit accepted the scope refusal on any operation that declared a 401 for the same reason. The 403 is added to the assembled path table rather than to stdResponses. That set is spread into most operations, about three hundred of them carry a 403 of their own, and most spread the set after it, so a shared 403 there would have overwritten every module-off and sharing refusal in the document. withScopeRefusal adds the shared response where an operation has none and prefixes an operation's own 403 with the gate's code, keeping the sharing sentence last. Operations that take no credential, and the three surfaces that authenticate by a credential of their own, are left as they were. The kit no longer tolerates the refusal on an operation's behalf, and a guard holds every operation behind the gate to declaring it. --- docs/api/openapi.yaml | 1751 ++++++++++------- docs/contributing/testing.md | 10 +- .../gate-refusal-publication.test.ts | 134 ++ src/lib/openapi/routes/index.ts | 9 +- src/lib/openapi/routes/shared.ts | 122 ++ tests/integration/routes/kit-contract.test.ts | 10 +- tests/kit/call-route.ts | 24 +- 7 files changed, 1371 insertions(+), 689 deletions(-) create mode 100644 src/lib/openapi/__tests__/gate-refusal-publication.test.ts diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 6cc0471ec..2ffb877dd 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -90,8 +90,10 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "403": &a2 description: >- - Cycle tracking is not enabled for this account (errorCode `cycle.disabled`). Resolved against the RECORD - being read, not the caller: a delegate inside somebody else's record sees the owner's setting. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Cycle tracking is not enabled for + this account (errorCode `cycle.disabled`). Resolved against the RECORD being read, not the caller: a + delegate inside somebody else's record sees the owner's setting. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -259,8 +261,10 @@ paths: $ref: "#/components/schemas/CycleCustomSymptomUpdatedEnvelope" "401": *a1 "403": &a3 - description: "Cycle tracking is not enabled for this account (errorCode `cycle.disabled`). Resolved against the RECORD - being read, not the caller: a delegate inside somebody else's record sees the owner's setting." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Cycle tracking is not enabled for + this account (errorCode `cycle.disabled`). Resolved against the RECORD being read, not the caller: a + delegate inside somebody else's record sees the owner's setting." content: application/json: schema: @@ -395,7 +399,7 @@ paths: "403": *a2 "409": *a7 "422": *a4 - "429": &a12 + "429": &a13 description: "Rate limit exceeded. This route shares one per-account bucket with every other authenticated write — `record-write:`, 300 requests per 60 seconds — keyed on the ACTING account, so a delegate burns their own allowance rather than the record owner's. Only the routes that meter themselves with a dedicated @@ -736,6 +740,15 @@ paths: schema: $ref: "#/components/schemas/CyclePrefsGetEnvelope" "401": *a1 + "403": &a9 + description: "The credential is valid but its scope does not reach this operation. `meta.errorCode` = + `auth.scope.insufficient`: a narrow Bearer token on a route that names no scope it holds, or a + connector-bound token on a write. Retrying with the same token will not help; use a session, or a token + minted with the scope the route names." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 patch: @@ -757,7 +770,7 @@ paths: application/json: schema: $ref: "#/components/schemas/CyclePrefsPatchEnvelope" - "400": &a14 + "400": &a15 description: The request body is not parseable JSON. Nothing was read and nothing was written. `meta.errorCode` names the surface that refused it (`.body.invalid_json`). This is a client-side serialisation fault, not a validation failure — a body that parses but fails the schema is the 422 beside this. @@ -766,6 +779,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/measurement-categories: @@ -785,6 +799,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementCategoriesEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/meta/capabilities: @@ -805,6 +820,7 @@ paths: schema: $ref: "#/components/schemas/CapabilitiesEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/version: @@ -877,7 +893,9 @@ paths: $ref: "#/components/schemas/AssistantFlagsEnvelope" "401": *a1 "403": - description: A selector header was attached to an actor surface (`meta.errorCode` = `sharing.not_permitted`). + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: A selector header was attached to + an actor surface (`meta.errorCode` = `sharing.not_permitted`)." content: application/json: schema: @@ -916,6 +934,7 @@ paths: schema: $ref: "#/components/schemas/UpdateCheckEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "502": @@ -964,16 +983,17 @@ paths: type: string format: binary "401": *a1 - "403": &a11 - description: "Refused: this request named a record the caller may not act on (`meta.errorCode` = - `sharing.access.denied`). Byte-identical for an account that does not exist, an account that granted - nothing, a grant whose sections do not reach this surface, and a read grant on a request that writes — the - response carries no reason, so it is not an account-enumeration oracle. The reason reaches the record - owner's activity feed and the operator's audit trail instead. On the cookie transport the same code answers - one further case: a session that has entered a shared record and sends a request without its record-context - assertion, which is a client older than the fence — leave the record and reload. A request that names no - record but the caller's own never reaches this response, so a client that never switches and never sends the - per-request account selector will not see it." + "403": &a12 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused: this request named a + record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). Byte-identical for an account + that does not exist, an account that granted nothing, a grant whose sections do not reach this surface, and + a read grant on a request that writes — the response carries no reason, so it is not an account-enumeration + oracle. The reason reaches the record owner's activity feed and the operator's audit trail instead. On the + cookie transport the same code answers one further case: a session that has entered a shared record and + sends a request without its record-context assertion, which is a client older than the fence — leave the + record and reload. A request that names no record but the caller's own never reaches this response, so a + client that never switches and never sends the per-request account selector will not see it." content: application/json: schema: @@ -1011,6 +1031,7 @@ paths: schema: $ref: "#/components/schemas/ShareLinkCreated" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 get: @@ -1027,6 +1048,7 @@ paths: schema: $ref: "#/components/schemas/ShareLinkList" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/share-links/{id}: @@ -1050,6 +1072,7 @@ paths: schema: $ref: "#/components/schemas/ShareLinkRevoked" "401": *a1 + "403": *a9 "404": description: Link not found (or owned by another user). content: @@ -1257,6 +1280,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/Patient: @@ -1286,6 +1310,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/Observation: @@ -1332,6 +1357,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/MedicationStatement: @@ -1361,6 +1387,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/MedicationAdministration: @@ -1390,6 +1417,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/Coverage: @@ -1420,6 +1448,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fhir/Patient/{id}: @@ -1445,6 +1474,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "404": description: OperationOutcome (application/fhir+json). content: @@ -1477,6 +1507,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "404": description: OperationOutcome (application/fhir+json). content: @@ -1510,6 +1541,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "404": description: OperationOutcome (application/fhir+json). content: @@ -1542,6 +1574,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "404": description: OperationOutcome (application/fhir+json). content: @@ -1574,6 +1607,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "404": description: OperationOutcome (application/fhir+json). content: @@ -1617,6 +1651,7 @@ paths: type: string format: binary "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/export: @@ -1676,6 +1711,7 @@ paths: schema: type: string "401": *a1 + "403": *a9 "422": description: "`format` or `type` carried a value outside its enum. The message names the accepted set; nothing is exported. Note that this route validates by hand rather than through Zod, so the response is the @@ -1719,6 +1755,7 @@ paths: schema: $ref: "#/components/schemas/FullBackupPayload" "401": *a1 + "403": *a9 "429": description: More than 10 exports in the trailing hour for this account (`export:`, shared across the export family). @@ -1779,6 +1816,7 @@ paths: schema: type: string "401": *a1 + "403": *a9 "429": description: More than 10 exports in the trailing hour for this account. content: @@ -1845,6 +1883,7 @@ paths: schema: type: string "401": *a1 + "403": *a9 "404": description: "`medicationId` named a medication this account does not hold. An unknown id and another account's id are indistinguishable." @@ -1900,6 +1939,7 @@ paths: schema: type: string "401": *a1 + "403": *a9 "429": description: More than 10 exports in the trailing hour for this account. content: @@ -1938,8 +1978,10 @@ paths: "401": *a1 "403": description: >- - The Bearer token carries a narrow scope. The encrypted archive needs a cookie session or a full-access - token; the refusal happens before any second-factor proof is looked at. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The Bearer token carries a narrow + scope. The encrypted archive needs a cookie session or a full-access token; the refusal happens before any + second-factor proof is looked at. Refused: the request was made while the session is acting on another account (`meta.errorCode` = @@ -2135,7 +2177,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": &a9 + "409": &a10 description: 'Refused before anything was checked or spent: the request stated `clientTransport.protocol: "http"` on a non-loopback host while this server issues `Secure` session cookies, so the browser would drop the session. `meta.errorCode` = `auth.session.insecure_transport`, `meta.sessionCookieSecure` = `true`. No password was @@ -2170,7 +2212,8 @@ paths: schema: $ref: "#/components/schemas/MfaVerifyResponse" "401": *a1 - "409": *a9 + "403": *a9 + "409": *a10 "422": *a4 "429": *a5 /api/auth/oidc/native/token: @@ -2203,6 +2246,7 @@ paths: schema: $ref: "#/components/schemas/OidcNativeTokenResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/native/login: @@ -2276,6 +2320,7 @@ paths: schema: $ref: "#/components/schemas/NativeHandoffTokenResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/step-up/options: @@ -2302,6 +2347,7 @@ paths: schema: $ref: "#/components/schemas/StepUpOptionsEnvelope" "401": *a1 + "403": *a9 "409": description: No passkey registered on this account. content: @@ -2370,6 +2416,7 @@ paths: schema: $ref: "#/components/schemas/StepUpMintEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/reproof: @@ -2414,6 +2461,7 @@ paths: schema: $ref: "#/components/schemas/ReproofEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 get: @@ -2432,6 +2480,7 @@ paths: schema: $ref: "#/components/schemas/ReproofStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa/totp/setup: @@ -2471,13 +2520,14 @@ paths: application/json: schema: $ref: "#/components/schemas/TotpSetupEnvelope" - "401": &a10 + "401": &a11 description: No session or elevation; or, on the cookie path, `auth.reproof.required` (no recent sign-in and no proof in the body) or `auth.reproof.failed` (the proof did not verify). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "409": description: A second factor is already active on this account. content: @@ -2514,6 +2564,7 @@ paths: schema: $ref: "#/components/schemas/TotpConfirmEnvelope" "401": *a1 + "403": *a9 "409": description: A second factor is already active, or enrollment was never started (no pending secret to confirm). content: @@ -2544,6 +2595,7 @@ paths: schema: $ref: "#/components/schemas/MfaDisableEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa/recovery-codes/regenerate: @@ -2562,6 +2614,7 @@ paths: schema: $ref: "#/components/schemas/MfaRecoveryRegenEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa: @@ -2581,6 +2634,7 @@ paths: schema: $ref: "#/components/schemas/MfaStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa/webauthn/register/options: @@ -2619,7 +2673,8 @@ paths: application/json: schema: $ref: "#/components/schemas/MfaWebauthnRegisterOptionsEnvelope" - "401": *a10 + "401": *a11 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa/webauthn/register/verify: @@ -2650,6 +2705,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/mfa/webauthn/{id}: @@ -2680,6 +2736,7 @@ paths: schema: $ref: "#/components/schemas/MfaWebauthnRenameEnvelope" "401": *a1 + "403": *a9 "404": description: No such security key for this account. content: @@ -2708,6 +2765,7 @@ paths: schema: $ref: "#/components/schemas/MfaWebauthnRemoveEnvelope" "401": *a1 + "403": *a9 "404": description: No such security key for this account. content: @@ -2737,6 +2795,7 @@ paths: schema: $ref: "#/components/schemas/MfaWebauthnLoginOptionsEnvelope" "401": *a1 + "403": *a9 "409": description: No security key is registered on the account — fall back to the TOTP / recovery-code challenge. content: @@ -2767,7 +2826,8 @@ paths: schema: $ref: "#/components/schemas/MfaWebauthnVerifyResponse" "401": *a1 - "409": *a9 + "403": *a9 + "409": *a10 "422": *a4 "429": *a5 /api/auth/passkey/login-verify: @@ -2852,6 +2912,7 @@ paths: - $ref: "#/components/schemas/RefreshResponse" - $ref: "#/components/schemas/RefreshRevokeResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/passkeys: @@ -2870,6 +2931,7 @@ paths: schema: $ref: "#/components/schemas/PasskeyListEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/sessions: @@ -2888,6 +2950,7 @@ paths: schema: $ref: "#/components/schemas/SessionListEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -2920,6 +2983,7 @@ paths: schema: $ref: "#/components/schemas/SignOutEverywhereEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/sessions/{id}: @@ -2943,6 +3007,7 @@ paths: schema: $ref: "#/components/schemas/SessionRevokeEnvelope" "401": *a1 + "403": *a9 "404": description: Session not found or not owned by the caller. content: @@ -2966,6 +3031,7 @@ paths: schema: $ref: "#/components/schemas/TrustedDeviceListEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -2981,6 +3047,7 @@ paths: schema: $ref: "#/components/schemas/TrustedDeviceRevokeAllEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/trusted-devices/{id}: @@ -3003,6 +3070,7 @@ paths: schema: $ref: "#/components/schemas/TrustedDeviceRevokeEnvelope" "401": *a1 + "403": *a9 "404": description: Trusted device not found or not owned by the caller. content: @@ -3036,6 +3104,7 @@ paths: schema: $ref: "#/components/schemas/SecurityActivityEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/logout: @@ -3124,7 +3193,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a9 + "409": *a10 "429": *a5 /api/auth/passkeys/{id}: patch: @@ -3166,6 +3235,7 @@ paths: schema: $ref: "#/components/schemas/PasskeyRenameEnvelope" "401": *a1 + "403": *a9 "404": description: No such passkey for this account. content: @@ -3228,6 +3298,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "404": description: No such passkey for this account. content: @@ -3264,6 +3335,7 @@ paths: schema: $ref: "#/components/schemas/ApiTokenListEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/tokens/measurements: @@ -3322,8 +3394,10 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": - description: The operator has switched the API off instance-wide (`AppSettings.apiGlobal`). Nothing was minted. A Bearer - caller does not reach this arm — it is refused 401 before the switch is read. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has switched the API + off instance-wide (`AppSettings.apiGlobal`). Nothing was minted. A Bearer caller does not reach this arm — + it is refused 401 before the switch is read." content: application/json: schema: @@ -3412,8 +3486,9 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": - description: The operator has switched the API off instance-wide. Nothing was minted. A Bearer caller is refused 401 - before the switch is read. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has switched the API + off instance-wide. Nothing was minted. A Bearer caller is refused 401 before the switch is read." content: application/json: schema: @@ -3499,8 +3574,9 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": - description: The operator has switched the API off instance-wide. Nothing was minted. A Bearer caller is refused 401 - before the switch is read. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has switched the API + off instance-wide. Nothing was minted. A Bearer caller is refused 401 before the switch is read." content: application/json: schema: @@ -3564,10 +3640,11 @@ paths: $ref: "#/components/schemas/ApiTokenRevokeEnvelope" "401": *a1 "403": - description: The operator has switched the API off instance-wide (`AppSettings.apiGlobal`). `meta.errorCode` is the - status default `http.forbidden`; nothing was revoked. The sibling list is deliberately not gated this way — - a token stays live while the switch is off, so its owner can still see it here even though this call will - not kill it. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has switched the API + off instance-wide (`AppSettings.apiGlobal`). `meta.errorCode` is the status default `http.forbidden`; + nothing was revoked. The sibling list is deliberately not gated this way — a token stays live while the + switch is off, so its owner can still see it here even though this call will not kill it." content: application/json: schema: @@ -3685,6 +3762,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "422": *a4 "429": *a5 /api/auth/profile: @@ -3721,6 +3799,7 @@ paths: schema: $ref: "#/components/schemas/AuthProfileUpdateEnvelope" "401": *a1 + "403": *a9 "409": description: The requested email belongs to another account (`meta.errorCode` = `profile.update.emailInUse`). Nothing was written. @@ -3736,7 +3815,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a16 + "429": &a17 description: "Too many email-address changes, and the request asked for nothing else. The address change shares one per-account bucket across both profile routes — `profile-email:`, 10 requests per 3600 seconds — keyed on the ACTING account, so an account cannot collect a fresh allowance by probing a different address. @@ -3933,6 +4012,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "409": description: The re-proof arm asked for a method the account has no credential for — fall back to another factor. content: @@ -3993,6 +4073,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "422": *a4 "429": *a5 /api/auth/codex/device-start: @@ -4020,6 +4101,7 @@ paths: schema: $ref: "#/components/schemas/CodexDeviceStartEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/codex/device-poll: @@ -4050,6 +4132,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "502": @@ -4080,6 +4163,7 @@ paths: schema: $ref: "#/components/schemas/CodexDisconnectEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/sync/state: @@ -4098,6 +4182,7 @@ paths: schema: $ref: "#/components/schemas/SyncStateEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/sync/changes: @@ -4137,6 +4222,7 @@ paths: schema: $ref: "#/components/schemas/SyncChangesEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/measurements/series-batch: @@ -4186,7 +4272,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementsSeriesBatchResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/measurements/series: @@ -4241,7 +4327,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementsSeriesResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/measurements: @@ -4462,7 +4548,7 @@ paths: schema: $ref: "#/components/schemas/ListMeasurementsResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -4518,7 +4604,7 @@ paths: schema: $ref: "#/components/schemas/CreateMeasurementResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": description: A measurement with this data already exists — the `(userId, type, source, externalId)` dedup index rejected the write. Never for a named sleep-stage body, which answers a re-post with 200 instead. @@ -4534,7 +4620,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a12 + "429": *a13 /api/measurements/{id}: get: tags: @@ -4555,7 +4641,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Measurement not found (or owned by another user). content: @@ -4603,7 +4689,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMeasurementResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Measurement not found (or owned by another user). content: @@ -4639,7 +4725,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMeasurementResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Measurement not found (or owned by another user). content: @@ -4683,6 +4769,7 @@ paths: schema: $ref: "#/components/schemas/BatchMeasurementsResponse" "401": *a1 + "403": *a9 "409": *a7 "422": description: "The batch exceeded the 500-entry limit (`measurement.batch.too_large`), failed validation @@ -4723,6 +4810,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/measurements/bulk-delete: @@ -4750,7 +4838,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementsBulkDeleteResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -4779,7 +4867,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementsRestoreResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -4812,11 +4900,13 @@ paths: schema: $ref: "#/components/schemas/SleepNightResponse" "401": *a1 - "403": &a13 + "403": &a14 description: >- - Module disabled for this account: the user (or operator) has the module turned off. `meta.errorCode` = - `module.disabled` and `meta.module` carries the disabled module key (e.g. "sleep"). Returned even for a - valid Bearer token. Clients hide the whole module surface end-to-end rather than retry. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: + the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` + carries the disabled module key (e.g. "sleep"). Returned even for a valid Bearer token. Clients hide the + whole module surface end-to-end rather than retry. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -4853,7 +4943,7 @@ paths: schema: $ref: "#/components/schemas/SleepRhythmResponse" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/workouts: @@ -4902,7 +4992,7 @@ paths: schema: $ref: "#/components/schemas/ListWorkoutsResponse" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/workouts/{id}: @@ -4938,10 +5028,12 @@ paths: schema: $ref: "#/components/schemas/GetWorkoutResponse" "401": *a1 - "403": &a19 - description: 'Module disabled for this account: the user (or operator) has the module turned off. `meta.errorCode` = - `module.disabled` and `meta.module` carries the disabled module key (e.g. "sleep"). Returned even for a - valid Bearer token. Clients hide the whole module surface end-to-end rather than retry.' + "403": &a21 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: + the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` + carries the disabled module key (e.g. \"sleep\"). Returned even for a valid Bearer token. Clients hide the + whole module surface end-to-end rather than retry." content: application/json: schema: @@ -4976,6 +5068,7 @@ paths: schema: $ref: "#/components/schemas/DeleteWorkoutResponse" "401": *a1 + "403": *a9 "404": description: Workout not found (or owned by another user). content: @@ -5038,6 +5131,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "409": *a7 "413": description: Request body exceeds the 5 MB ceiling. @@ -5095,7 +5189,7 @@ paths: schema: $ref: "#/components/schemas/ActivityCalendarEnvelope" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/activity/goals: @@ -5113,6 +5207,7 @@ paths: schema: $ref: "#/components/schemas/ActivityGoalsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -5135,8 +5230,9 @@ paths: schema: $ref: "#/components/schemas/ActivityGoalsPatchEnvelope" "401": *a1 + "403": *a9 "422": *a4 - "429": *a12 + "429": *a13 /api/measurements/provenance: get: tags: @@ -5261,7 +5357,7 @@ paths: schema: $ref: "#/components/schemas/DayProvenanceEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/measurements/provenance/compare: @@ -5439,7 +5535,7 @@ paths: schema: $ref: "#/components/schemas/SourceComparisonEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/devices: @@ -5463,6 +5559,7 @@ paths: schema: $ref: "#/components/schemas/DeviceRegisterResponse" "401": *a1 + "403": *a9 "409": description: Device or APNs token already registered to another user. content: @@ -5497,6 +5594,7 @@ paths: schema: $ref: "#/components/schemas/DeviceRevokeEnvelope" "401": *a1 + "403": *a9 "404": description: No such device for this caller — also the answer for one already revoked, so a retry after a successful revoke is a 404 rather than an idempotent 200. @@ -5549,7 +5647,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeAggregateEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": description: The query failed validation — a missing or unknown `scope`, or a `days` outside 1..365. Multi-issue envelope; an audit breadcrumb is filed under the RECORD as `medications.intake.list.validation-failed` with @@ -5639,9 +5737,11 @@ paths: "401": *a1 "403": description: >- - Refused: a delegate tried to snooze, or to change a dose the owner had already recorded as taken or skipped - (`meta.errorCode` = `sharing.not_permitted`). Both stay open to the owner, and the attempt is filed on the - owner's activity trail. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused: a delegate tried to + snooze, or to change a dose the owner had already recorded as taken or skipped (`meta.errorCode` = + `sharing.not_permitted`). Both stay open to the owner, and the attempt is filed on the owner's activity + trail. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -5677,7 +5777,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a12 + "429": *a13 /api/medications/{id}/glp1: get: tags: @@ -5702,7 +5802,7 @@ paths: schema: $ref: "#/components/schemas/Glp1DetailResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -5786,7 +5886,7 @@ paths: schema: $ref: "#/components/schemas/Glp1PostResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -5821,7 +5921,9 @@ paths: $ref: "#/components/schemas/MedicationApiEndpointStateEnvelope" "401": *a1 "403": - description: The operator has disabled the API globally. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API + globally." content: application/json: schema: @@ -5875,7 +5977,9 @@ paths: $ref: "#/components/schemas/MedicationApiEndpointCreatedEnvelope" "401": *a1 "403": - description: The operator has disabled the API globally. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API + globally." content: application/json: schema: @@ -5921,7 +6025,7 @@ paths: schema: $ref: "#/components/schemas/ReminderPhaseConfigEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -5959,6 +6063,7 @@ paths: schema: $ref: "#/components/schemas/ReminderPhaseConfigSavedEnvelope" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another account). content: @@ -5987,6 +6092,7 @@ paths: schema: $ref: "#/components/schemas/ReminderPhaseConfigResetEnvelope" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another account). content: @@ -6037,7 +6143,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationSideEffectsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -6076,7 +6182,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationSideEffectEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -6121,7 +6227,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationSideEffectEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No such entry for this medication and this record. An unknown id, another medication's entry and another record's entry are indistinguishable. @@ -6169,7 +6275,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationIntakeEventEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No live event with that id under this medication and record. An unknown id and an already-tombstoned one are indistinguishable. @@ -6217,7 +6323,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationIntakeEventEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No event with that id under this medication and record. content: @@ -6260,7 +6366,7 @@ paths: schema: $ref: "#/components/schemas/BulkDeleteMedicationIntakeEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -6302,7 +6408,7 @@ paths: schema: $ref: "#/components/schemas/PurgeMedicationIntakeEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -6347,7 +6453,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportAcceptedEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another account). content: @@ -6407,6 +6513,7 @@ paths: schema: $ref: "#/components/schemas/BulkMedicationIntakeResponseEnvelope" "401": *a1 + "403": *a9 "409": *a7 "422": *a4 "429": *a5 @@ -6427,7 +6534,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationsResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -6459,7 +6566,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -6479,7 +6586,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationCategoriesResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -6506,7 +6613,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationCategoryResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -6539,6 +6646,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationCategoryResponse" "401": *a1 + "403": *a9 "404": description: Not one of the record's own categories. content: @@ -6570,6 +6678,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationCategoryResponse" "401": *a1 + "403": *a9 "404": description: Not one of the record's own categories. content: @@ -6598,6 +6707,7 @@ paths: schema: $ref: "#/components/schemas/MedicationCoursesResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or another user's). content: @@ -6635,6 +6745,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationCourseResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or another user's). content: @@ -6676,6 +6787,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationCourseResponse" "401": *a1 + "403": *a9 "404": description: Course not found (`medications.course.notFound`), or another user's. content: @@ -6709,6 +6821,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationCourseResponse" "401": *a1 + "403": *a9 "404": description: Course not found (`medications.course.notFound`), or another user's. content: @@ -6733,7 +6846,7 @@ paths: schema: $ref: "#/components/schemas/MedicationListLayoutResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 put: @@ -6759,6 +6872,7 @@ paths: schema: $ref: "#/components/schemas/MedicationListLayoutSaved" "401": *a1 + "403": *a9 "409": description: Medications layout changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -6767,7 +6881,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a15 + "422": &a16 description: Request validation failed. When the body carried a `baseUpdatedAt` that could not be parsed as an ISO-8601 timestamp — including an explicit `null` — `meta.errorCode` = `invalid_base_updated_at` and nothing was written. Omit the key entirely for the unconditional write; do not send `null` for it. @@ -6789,6 +6903,7 @@ paths: schema: $ref: "#/components/schemas/MedicationListLayoutReset" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/medications/{id}: @@ -6812,7 +6927,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -6853,7 +6968,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -6882,6 +6997,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another user). content: @@ -6926,7 +7042,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationIntakeResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -6944,7 +7060,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a12 + "429": *a13 get: tags: - Medications @@ -7009,7 +7125,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationIntakeEventsResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7042,7 +7158,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationInventoryResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7078,7 +7194,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationInventoryItemResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7122,7 +7238,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationInventoryItemResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Inventory item not found (or owned by another user / medication). content: @@ -7157,7 +7273,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationInventoryItemResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Inventory item not found (or owned by another user / medication). content: @@ -7193,14 +7309,16 @@ paths: $ref: "#/components/schemas/MedicationExtractResponse" "401": *a1 "403": - description: "AI refusal: the `medicationExtract` capability is unavailable for this record. The envelope carries - `meta.errorCode`, `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). - `assistant.disabled.documentAi` — the operator turned off reading documents, or every AI feature (the master - switch). `ai.record.notPermitted` — AI work is not admitted inside this record (a delegate acting in - somebody else's record). `consent.ai.required` — the provider this request would reach leaves the machine - and the record holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The - question is answered for the provider actually picked, so it can differ from the published capability when a - text-mode read takes a different provider than a vision read would." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the + `medicationExtract` capability is unavailable for this record. The envelope carries `meta.errorCode`, + `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). `assistant.disabled.documentAi` — the + operator turned off reading documents, or every AI feature (the master switch). `ai.record.notPermitted` — + AI work is not admitted inside this record (a delegate acting in somebody else's record). + `consent.ai.required` — the provider this request would reach leaves the machine and the record holds no + active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The question is answered + for the provider actually picked, so it can differ from the published capability when a text-mode read takes + a different provider than a vision read would." content: application/json: schema: @@ -7253,7 +7371,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationCadenceResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7302,7 +7420,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationComplianceResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/medications/{id}/compliance: @@ -7329,6 +7447,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationComplianceResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another user). content: @@ -7361,6 +7480,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationEfficacyResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another user). content: @@ -7397,6 +7517,7 @@ paths: schema: $ref: "#/components/schemas/SetMedicationEfficacyTargetResponse" "401": *a1 + "403": *a9 "404": description: Medication or biomarker not found. content: @@ -7453,6 +7574,7 @@ paths: schema: $ref: "#/components/schemas/GetDoseHistoryResponse" "401": *a1 + "403": *a9 "404": description: Medication not found (or owned by another user). content: @@ -7483,7 +7605,7 @@ paths: schema: $ref: "#/components/schemas/ListScheduleRevisionsResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7521,7 +7643,7 @@ paths: schema: $ref: "#/components/schemas/CreateScheduleRevisionResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication not found (or owned by another user). content: @@ -7565,7 +7687,7 @@ paths: schema: $ref: "#/components/schemas/UpdateScheduleRevisionResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication or revision not found (or owned by another user). content: @@ -7606,7 +7728,7 @@ paths: schema: $ref: "#/components/schemas/DeleteScheduleRevisionResponse" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Medication or revision not found (or owned by another user). content: @@ -7669,7 +7791,7 @@ paths: schema: $ref: "#/components/schemas/MedicationDoseHistoryImportQueuedEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": description: A dose-history import is already in progress. content: @@ -7731,7 +7853,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportJobStatusEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No such job for this medication (or owned by another user). content: @@ -7765,7 +7887,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportJobStatusEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No such job for this account (or owned by another user). content: @@ -7795,7 +7917,7 @@ paths: schema: $ref: "#/components/schemas/ProfileSummaryEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/ai/test: @@ -7844,8 +7966,10 @@ paths: $ref: "#/components/schemas/AiTestEnvelope" "401": *a1 "403": - description: The operator's master AI switch is off (`meta.errorCode = "assistant.disabled.enabled"`), or the switches - could not be read (`ai.unavailable`). Nothing was sent. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator's master AI switch + is off (`meta.errorCode = \"assistant.disabled.enabled\"`), or the switches could not be read + (`ai.unavailable`). Nothing was sent." content: application/json: schema: @@ -7896,6 +8020,7 @@ paths: schema: $ref: "#/components/schemas/GetAiProviderResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -7939,6 +8064,7 @@ paths: schema: $ref: "#/components/schemas/PatchAiProviderResponse" "401": *a1 + "403": *a9 "413": description: Body exceeds 64 KiB. content: @@ -7978,6 +8104,7 @@ paths: schema: $ref: "#/components/schemas/GetCoachPrefsResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -8000,8 +8127,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PutCoachPrefsResponse" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "409": description: Coach preferences changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -8010,7 +8138,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 /api/auth/me/disable-coach: get: @@ -8027,6 +8155,7 @@ paths: schema: $ref: "#/components/schemas/GetDisableCoachResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -8048,8 +8177,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchDisableCoachResponse" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/diabetes: @@ -8069,6 +8199,7 @@ paths: schema: $ref: "#/components/schemas/GetDiabetesResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -8092,6 +8223,7 @@ paths: schema: $ref: "#/components/schemas/PatchDiabetesResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/health-score-config: @@ -8111,6 +8243,7 @@ paths: schema: $ref: "#/components/schemas/HealthScoreConfigEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -8139,6 +8272,7 @@ paths: schema: $ref: "#/components/schemas/PatchHealthScoreConfigEnvelope" "401": *a1 + "403": *a9 "409": description: Score selection changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -8179,6 +8313,7 @@ paths: schema: $ref: "#/components/schemas/GetModulesResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -8203,8 +8338,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchModulesResponse" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "409": description: Module preferences changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -8213,7 +8349,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 /api/auth/me/source-priority: get: @@ -8230,6 +8366,7 @@ paths: schema: $ref: "#/components/schemas/GetSourcePriorityResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -9164,8 +9301,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PutSourcePriorityResponse" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/user/avatar: @@ -9198,6 +9336,7 @@ paths: schema: $ref: "#/components/schemas/UploadAvatarResponse" "401": *a1 + "403": *a9 "413": description: Upload exceeds the 2 MiB byte limit or the 2048×2048 dimension limit. content: @@ -9222,6 +9361,7 @@ paths: "204": description: Avatar cleared (or already empty). "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/user/avatar/{id}: @@ -9258,7 +9398,9 @@ paths: description: Raw WebP bytes. "401": *a1 "403": - description: Caller is not the avatar's owner. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Caller is not the avatar's + owner." content: application/json: schema: @@ -9286,6 +9428,7 @@ paths: schema: $ref: "#/components/schemas/GetProfileResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9309,6 +9452,7 @@ paths: schema: $ref: "#/components/schemas/PatchProfileResponse" "401": *a1 + "403": *a9 "409": description: The requested email belongs to another account (`meta.errorCode` = `profile.update.emailInUse`). Nothing was written. Shared with PUT /api/auth/profile, which runs the same handler. @@ -9317,7 +9461,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a16 + "429": *a17 /api/auth/me/notification-prefs: get: tags: @@ -9334,6 +9478,7 @@ paths: schema: $ref: "#/components/schemas/GetNotificationPrefsResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9359,8 +9504,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchNotificationPrefsResponse" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "409": description: Notification preferences changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -9369,7 +9515,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 /api/auth/me/report-selection: get: @@ -9387,6 +9533,7 @@ paths: schema: $ref: "#/components/schemas/GetReportSelectionResponseEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -9411,8 +9558,9 @@ paths: application/json: schema: $ref: "#/components/schemas/PutReportSelectionResponseEnvelope" - "400": *a14 + "400": *a15 "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/unit-preference: @@ -9432,6 +9580,7 @@ paths: schema: $ref: "#/components/schemas/GetUnitPreferenceEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9459,6 +9608,7 @@ paths: schema: $ref: "#/components/schemas/PatchUnitPreferenceEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -9484,6 +9634,7 @@ paths: schema: $ref: "#/components/schemas/GetGlucoseUnitEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9515,6 +9666,7 @@ paths: schema: $ref: "#/components/schemas/PatchGlucoseUnitEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -9539,6 +9691,7 @@ paths: schema: $ref: "#/components/schemas/GetDocumentsAutoAiReadEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9576,6 +9729,7 @@ paths: schema: $ref: "#/components/schemas/PatchDocumentsAutoAiReadEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -9600,6 +9754,7 @@ paths: schema: $ref: "#/components/schemas/GetInjectionSitePrefsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9628,6 +9783,7 @@ paths: schema: $ref: "#/components/schemas/PatchInjectionSitePrefsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/user/thresholds: @@ -9652,6 +9808,7 @@ paths: schema: $ref: "#/components/schemas/GetThresholdsEnvelope" "401": *a1 + "403": *a9 "404": description: The authenticated user row could not be loaded. In practice unreachable — the session that resolved the caller was validated against that row. @@ -9689,6 +9846,7 @@ paths: schema: $ref: "#/components/schemas/PutThresholdsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -9732,6 +9890,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": description: '`metric` was omitted and the body did not carry `{ "confirm": "RESET_THRESHOLDS" }`. Nothing was reset.' content: @@ -9760,6 +9919,7 @@ paths: schema: $ref: "#/components/schemas/DeviceListEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/devices/{id}: @@ -9790,6 +9950,7 @@ paths: schema: $ref: "#/components/schemas/DevicePatchEnvelope" "401": *a1 + "403": *a9 "404": description: No such device for this account. content: @@ -9828,6 +9989,7 @@ paths: schema: $ref: "#/components/schemas/AccountDeviceRevokeEnvelope" "401": *a1 + "403": *a9 "404": description: No such device for this account. content: @@ -9854,6 +10016,7 @@ paths: schema: $ref: "#/components/schemas/GetLabsLocalOcrEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9875,6 +10038,7 @@ paths: schema: $ref: "#/components/schemas/PatchLabsLocalOcrEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -9899,6 +10063,7 @@ paths: schema: $ref: "#/components/schemas/GetWorkoutRoutesEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9921,6 +10086,7 @@ paths: schema: $ref: "#/components/schemas/PatchWorkoutRoutesEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -9944,6 +10110,7 @@ paths: schema: $ref: "#/components/schemas/GetUseCentralCodexEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -9972,6 +10139,7 @@ paths: schema: $ref: "#/components/schemas/PatchUseCentralCodexEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 1024 bytes. Nothing was written. content: @@ -10017,7 +10185,9 @@ paths: $ref: "#/components/schemas/LocalePutEnvelope" "401": *a1 "403": - description: A selector header was attached to an actor surface (`meta.errorCode` = `sharing.not_permitted`). + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: A selector header was attached to + an actor surface (`meta.errorCode` = `sharing.not_permitted`)." content: application/json: schema: @@ -10053,6 +10223,7 @@ paths: schema: $ref: "#/components/schemas/TimezonePutEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/auth/me/passkey-nudge/dismiss: @@ -10070,6 +10241,7 @@ paths: schema: $ref: "#/components/schemas/PasskeyNudgeDismissEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/chat: @@ -10116,6 +10288,7 @@ paths: schema: $ref: "#/components/schemas/CoachConversationsPageResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 post: @@ -10160,11 +10333,13 @@ paths: $ref: "#/components/schemas/CoachStreamEvent" "401": *a1 "403": - description: "The `coach` AI capability is unavailable for this record. `meta.capability` = `coach` and `meta.reason` - says which layer refused: the operator's switch (`assistant.disabled.coach`, or `assistant.disabled.enabled` - for the master switch), an owning module or the person's own AI opt-out (`module.disabled` with - `meta.module`), a delegate inside somebody else's record (`ai.record.notPermitted`), or a missing consent - receipt (`consent.ai.required`). Nothing was generated, read from a stored model answer, or enqueued." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The `coach` AI capability is + unavailable for this record. `meta.capability` = `coach` and `meta.reason` says which layer refused: the + operator's switch (`assistant.disabled.coach`, or `assistant.disabled.enabled` for the master switch), an + owning module or the person's own AI opt-out (`module.disabled` with `meta.module`), a delegate inside + somebody else's record (`ai.record.notPermitted`), or a missing consent receipt (`consent.ai.required`). + Nothing was generated, read from a stored model answer, or enqueued." content: application/json: schema: @@ -10177,7 +10352,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a18 + "503": &a20 description: "The AI capability could not be resolved (its inputs could not be read) and the answer failed closed: `meta.errorCode` = `ai.unavailable`, `meta.reason` = `check_failed`. Retry later; no data depends on it." content: @@ -10217,6 +10392,7 @@ paths: schema: $ref: "#/components/schemas/CoachMessageResultsResponse" "401": *a1 + "403": *a9 "404": description: Conversation or message not found, or not owned by the caller. content: @@ -10260,6 +10436,7 @@ paths: schema: $ref: "#/components/schemas/CoachMessageTrailResponse" "401": *a1 + "403": *a9 "404": description: Conversation or message not found, or not owned by the caller. content: @@ -10292,6 +10469,7 @@ paths: schema: $ref: "#/components/schemas/CoachConversationDetailResponse" "401": *a1 + "403": *a9 "404": description: Conversation not found or not owned by the caller. content: @@ -10331,6 +10509,7 @@ paths: schema: $ref: "#/components/schemas/CoachConversationRenamed" "401": *a1 + "403": *a9 "404": description: Conversation not found or not owned by the caller. content: @@ -10366,6 +10545,7 @@ paths: schema: $ref: "#/components/schemas/CoachConversationDeleted" "401": *a1 + "403": *a9 "404": description: Conversation not found or not owned by the caller. content: @@ -10408,20 +10588,21 @@ paths: description: "SSE frames: `data: \\n\\n`. See the operation description for the per-`type` frame shapes." "401": *a1 "403": - description: "AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries - `meta.errorCode`, `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). - `assistant.disabled.documentAi` — the operator turned off reading documents, or every AI feature (the master - switch). `module.disabled` with `meta.module = \"inboundDocuments\"` — the module is off for this record. - `ai.record.notPermitted` — AI work is not admitted inside this record (a delegate acting in somebody else's - record). `consent.ai.required` — the provider this request would reach leaves the machine and the record - holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The question is - answered for the provider actually picked, so it can differ from the published capability when a text-mode - read takes a different provider than a vision read would. The route also answers under `coach`, with the - same envelope and that capability's own codes (`assistant.disabled.coach`, `module.disabled` with - `meta.module = \"coach\"` when the Coach is off for the record). A 422 (`stdResponses`) covers an un-indexed - / unavailable attachment (`coach.fenced.attachmentUnavailable`), the attachment cap - (`coach.fenced.attachmentLimit`), or `attachmentIds` sent with a `conversationId` - (`coach.fenced.attachmentConflict`)." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` + capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and + `meta.reason` (one of `AiUnavailableReason`). `assistant.disabled.documentAi` — the operator turned off + reading documents, or every AI feature (the master switch). `module.disabled` with `meta.module = + \"inboundDocuments\"` — the module is off for this record. `ai.record.notPermitted` — AI work is not + admitted inside this record (a delegate acting in somebody else's record). `consent.ai.required` — the + provider this request would reach leaves the machine and the record holds no active `ai_extraction` or + `ai_full` receipt; only a self-hosted model is exempt. The question is answered for the provider actually + picked, so it can differ from the published capability when a text-mode read takes a different provider than + a vision read would. The route also answers under `coach`, with the same envelope and that capability's own + codes (`assistant.disabled.coach`, `module.disabled` with `meta.module = \"coach\"` when the Coach is off + for the record). A 422 (`stdResponses`) covers an un-indexed / unavailable attachment + (`coach.fenced.attachmentUnavailable`), the attachment cap (`coach.fenced.attachmentLimit`), or + `attachmentIds` sent with a `conversationId` (`coach.fenced.attachmentConflict`)." content: application/json: schema: @@ -10469,17 +10650,19 @@ paths: $ref: "#/components/schemas/CoachAttachmentsResponse" "401": *a1 "403": - description: "AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries - `meta.errorCode`, `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). - `assistant.disabled.documentAi` — the operator turned off reading documents, or every AI feature (the master - switch). `module.disabled` with `meta.module = \"inboundDocuments\"` — the module is off for this record. - `ai.record.notPermitted` — AI work is not admitted inside this record (a delegate acting in somebody else's - record). `consent.ai.required` — the provider this request would reach leaves the machine and the record - holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The question is - answered for the provider actually picked, so it can differ from the published capability when a text-mode - read takes a different provider than a vision read would. The route also answers under `coach`, with the - same envelope and that capability's own codes (`assistant.disabled.coach`, `module.disabled` with - `meta.module = \"coach\"` when the Coach is off for the record)." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` + capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and + `meta.reason` (one of `AiUnavailableReason`). `assistant.disabled.documentAi` — the operator turned off + reading documents, or every AI feature (the master switch). `module.disabled` with `meta.module = + \"inboundDocuments\"` — the module is off for this record. `ai.record.notPermitted` — AI work is not + admitted inside this record (a delegate acting in somebody else's record). `consent.ai.required` — the + provider this request would reach leaves the machine and the record holds no active `ai_extraction` or + `ai_full` receipt; only a self-hosted model is exempt. The question is answered for the provider actually + picked, so it can differ from the published capability when a text-mode read takes a different provider than + a vision read would. The route also answers under `coach`, with the same envelope and that capability's own + codes (`assistant.disabled.coach`, `module.disabled` with `meta.module = \"coach\"` when the Coach is off + for the record)." content: application/json: schema: @@ -10529,6 +10712,7 @@ paths: schema: $ref: "#/components/schemas/CoachAttachmentsResponseDetach" "401": *a1 + "403": *a9 "404": description: Attachment or conversation not found / not owned. content: @@ -10553,6 +10737,7 @@ paths: schema: $ref: "#/components/schemas/InsightsLayout" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -10577,6 +10762,7 @@ paths: schema: $ref: "#/components/schemas/InsightsLayoutSaved" "401": *a1 + "403": *a9 "409": description: Insights layout changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -10585,7 +10771,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 delete: tags: @@ -10600,6 +10786,7 @@ paths: schema: $ref: "#/components/schemas/InsightsLayoutReset" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/coach/facts: @@ -10621,6 +10808,7 @@ paths: schema: $ref: "#/components/schemas/CoachFactsList" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 post: @@ -10653,9 +10841,11 @@ paths: $ref: "#/components/schemas/CoachFactCreated" "401": *a1 "403": - description: 'Module disabled for this account: the user (or operator) has the module turned off. `meta.errorCode` = - `module.disabled` and `meta.module` carries the disabled module key (e.g. "sleep"). Returned even for a - valid Bearer token. Clients hide the whole module surface end-to-end rather than retry.' + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: + the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` + carries the disabled module key (e.g. \"sleep\"). Returned even for a valid Bearer token. Clients hide the + whole module surface end-to-end rather than retry." content: application/json: schema: @@ -10682,6 +10872,7 @@ paths: schema: $ref: "#/components/schemas/CoachFactsCleared" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/coach/facts/{id}: @@ -10715,6 +10906,7 @@ paths: schema: $ref: "#/components/schemas/CoachFactUpdated" "401": *a1 + "403": *a9 "404": description: Fact not found or not owned by the caller. content: @@ -10738,6 +10930,7 @@ paths: schema: $ref: "#/components/schemas/CoachFactDeleted" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/coach/nudge-status: @@ -10760,7 +10953,7 @@ paths: schema: $ref: "#/components/schemas/CoachNudgeStatus" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/coach/seen: @@ -10781,6 +10974,7 @@ paths: schema: $ref: "#/components/schemas/CoachSeenResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/coach/about-me: @@ -10803,6 +10997,7 @@ paths: schema: $ref: "#/components/schemas/GetCoachAboutMeResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -10870,6 +11065,7 @@ paths: schema: $ref: "#/components/schemas/PutCoachAboutMeResponse" "401": *a1 + "403": *a9 "409": description: Self-context changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -10878,7 +11074,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 /api/anamnesis/facts: get: @@ -10897,7 +11093,7 @@ paths: schema: $ref: "#/components/schemas/GetAnamnesisFactsResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -11315,6 +11511,7 @@ paths: schema: $ref: "#/components/schemas/CreateAnamnesisFactResponse" "401": *a1 + "403": *a9 "409": description: A current revision already exists for this fact kind. `meta.errorCode` = `anamnesis.fact.currentExists`. content: @@ -11370,7 +11567,8 @@ paths: schema: $ref: "#/components/schemas/CorrectAnamnesisFactResponse" "401": *a1 - "404": &a17 + "403": *a9 + "404": &a18 description: No current revision with this id exists for the authenticated account. content: application/json: @@ -11406,7 +11604,8 @@ paths: schema: $ref: "#/components/schemas/RemoveAnamnesisFactResponse" "401": *a1 - "404": *a17 + "403": *a9 + "404": *a18 "409": description: Anamnesis fact changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -11433,6 +11632,7 @@ paths: schema: $ref: "#/components/schemas/GetEmergencyProfileResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -11455,6 +11655,7 @@ paths: schema: $ref: "#/components/schemas/UpdateEmergencyProfileResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/coach/about-me/questions: @@ -11473,6 +11674,7 @@ paths: schema: $ref: "#/components/schemas/GetCoachAboutMeQuestionsResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -11499,6 +11701,7 @@ paths: schema: $ref: "#/components/schemas/DeleteCoachAboutMeQuestionsResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/coach/about-me/adopt: @@ -11548,9 +11751,11 @@ paths: $ref: "#/components/schemas/CoachAboutMeAdoptResponse" "401": *a1 "403": - description: 'Module disabled for this account: the user (or operator) has the module turned off. `meta.errorCode` = - `module.disabled` and `meta.module` carries the disabled module key (e.g. "sleep"). Returned even for a - valid Bearer token. Clients hide the whole module surface end-to-end rather than retry.' + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: + the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` + carries the disabled module key (e.g. \"sleep\"). Returned even for a valid Bearer token. Clients hide the + whole module surface end-to-end rather than retry." content: application/json: schema: @@ -11623,6 +11828,7 @@ paths: schema: $ref: "#/components/schemas/CoachPlansList" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/coach/plans/{id}: @@ -11657,7 +11863,8 @@ paths: $ref: "#/components/schemas/CoachPlanUpdated" "401": *a1 "403": - description: Coach surface disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface disabled." content: application/json: schema: @@ -11694,7 +11901,8 @@ paths: $ref: "#/components/schemas/CoachPlanDeleted" "401": *a1 "403": - description: Coach surface disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface disabled." content: application/json: schema: @@ -11733,7 +11941,9 @@ paths: $ref: "#/components/schemas/CoachReminderSuggestionResultCreated" "401": *a1 "403": - description: Coach surface (or the cadence's module) disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface (or the cadence's + module) disabled." content: application/json: schema: @@ -11766,7 +11976,7 @@ paths: schema: $ref: "#/components/schemas/CoachRemindersList" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -11792,7 +12002,8 @@ paths: $ref: "#/components/schemas/CoachReminderCreated" "401": *a1 "403": - description: Coach surface disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface disabled." content: application/json: schema: @@ -11836,7 +12047,8 @@ paths: $ref: "#/components/schemas/CoachReminderUpdated" "401": *a1 "403": - description: Coach surface disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface disabled." content: application/json: schema: @@ -11872,6 +12084,7 @@ paths: schema: $ref: "#/components/schemas/CoachReminderDeleted" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/coach/suggested-actions: @@ -11900,7 +12113,8 @@ paths: $ref: "#/components/schemas/CoachSuggestedActionResultCreated" "401": *a1 "403": - description: Coach surface disabled. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Coach surface disabled." content: application/json: schema: @@ -11930,6 +12144,12 @@ paths: schema: $ref: "#/components/schemas/AdminInviteList" "401": *a1 + "403": &a19 + description: The session is valid but its account is not an administrator. `meta.errorCode` = `auth.admin.required`. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 post: @@ -11965,6 +12185,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteCreated" "401": *a1 + "403": *a19 "422": *a4 "429": *a5 /api/admin/invites/{id}: @@ -11989,6 +12210,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteRevoked" "401": *a1 + "403": *a19 "422": *a4 "429": *a5 /api/insights/chat/messages/{id}/feedback: @@ -12013,6 +12235,7 @@ paths: schema: $ref: "#/components/schemas/CoachMessageFeedbackResponse" "401": *a1 + "403": *a9 "404": description: Message not found or not owned by the caller. content: @@ -12043,6 +12266,7 @@ paths: schema: $ref: "#/components/schemas/AdminMeasurementMaintenanceStatusResponse" "401": *a1 + "403": *a19 "422": *a4 "429": *a5 post: @@ -12068,7 +12292,8 @@ paths: $ref: "#/components/schemas/AdminMeasurementMaintenanceResponse" "401": *a1 "403": - description: Caller is not an admin. + description: "`meta.errorCode` = `auth.admin.required` when the session's account is not an administrator; the admin + gate answers it before the route's own checks run. Otherwise: Caller is not an admin." content: application/json: schema: @@ -12118,7 +12343,8 @@ paths: $ref: "#/components/schemas/AdminReminderCheckResponse" "401": *a1 "403": - description: Caller is not an admin. + description: "`meta.errorCode` = `auth.admin.required` when the session's account is not an administrator; the admin + gate answers it before the route's own checks run. Otherwise: Caller is not an admin." content: application/json: schema: @@ -12142,7 +12368,8 @@ paths: $ref: "#/components/schemas/AdminDiagnosticResponse" "401": *a1 "403": - description: Caller is not an admin. + description: "`meta.errorCode` = `auth.admin.required` when the session's account is not an administrator; the admin + gate answers it before the route's own checks run. Otherwise: Caller is not an admin." content: application/json: schema: @@ -12169,7 +12396,7 @@ paths: schema: $ref: "#/components/schemas/DashboardSnapshotResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/comprehensive: @@ -12191,7 +12418,7 @@ paths: schema: $ref: "#/components/schemas/InsightsComprehensiveResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/cards: @@ -12212,7 +12439,7 @@ paths: schema: $ref: "#/components/schemas/InsightsCardsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/pregenerate: @@ -12244,6 +12471,7 @@ paths: schema: $ref: "#/components/schemas/InsightsPregenerateResponseEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/analytics: @@ -12294,6 +12522,7 @@ paths: schema: $ref: "#/components/schemas/AnalyticsEnvelope" "401": *a1 + "403": *a9 "422": description: "`slice` carried a value outside the closed set. `meta.errorCode` = `analytics.invalid_query`, with the offending path under `details.issues`." @@ -12329,12 +12558,13 @@ paths: $ref: "#/components/schemas/InsightsGenerateReadEnvelope" "401": *a1 "403": - description: "The `briefing` AI capability is unavailable for this record. `meta.capability` = `briefing` and - `meta.reason` says which layer refused: the operator's switch (`assistant.disabled.briefing`, or - `assistant.disabled.enabled` for the master switch), an owning module or the person's own AI opt-out - (`module.disabled` with `meta.module`), a delegate inside somebody else's record (`ai.record.notPermitted`), - or a missing consent receipt (`consent.ai.required`). Nothing was generated, read from a stored model - answer, or enqueued." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The `briefing` AI capability is + unavailable for this record. `meta.capability` = `briefing` and `meta.reason` says which layer refused: the + operator's switch (`assistant.disabled.briefing`, or `assistant.disabled.enabled` for the master switch), an + owning module or the person's own AI opt-out (`module.disabled` with `meta.module`), a delegate inside + somebody else's record (`ai.record.notPermitted`), or a missing consent receipt (`consent.ai.required`). + Nothing was generated, read from a stored model answer, or enqueued." content: application/json: schema: @@ -12347,7 +12577,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a18 + "503": *a20 post: tags: - Insights @@ -12382,12 +12612,13 @@ paths: $ref: "#/components/schemas/InsightsGeneratePostEnvelope" "401": *a1 "403": - description: "The `briefing` AI capability is unavailable for this record. `meta.capability` = `briefing` and - `meta.reason` says which layer refused: the operator's switch (`assistant.disabled.briefing`, or - `assistant.disabled.enabled` for the master switch), an owning module or the person's own AI opt-out - (`module.disabled` with `meta.module`), a delegate inside somebody else's record (`ai.record.notPermitted`), - or a missing consent receipt (`consent.ai.required`). Nothing was generated, read from a stored model - answer, or enqueued." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The `briefing` AI capability is + unavailable for this record. `meta.capability` = `briefing` and `meta.reason` says which layer refused: the + operator's switch (`assistant.disabled.briefing`, or `assistant.disabled.enabled` for the master switch), an + owning module or the person's own AI opt-out (`module.disabled` with `meta.module`), a delegate inside + somebody else's record (`ai.record.notPermitted`), or a missing consent receipt (`consent.ai.required`). + Nothing was generated, read from a stored model answer, or enqueued." content: application/json: schema: @@ -12445,6 +12676,7 @@ paths: schema: $ref: "#/components/schemas/ProviderChainEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -12471,6 +12703,7 @@ paths: schema: $ref: "#/components/schemas/ProviderChainSavedEnvelope" "401": *a1 + "403": *a9 "422": description: The body was not a valid chain — an empty array, more entries than there are provider types, an unknown provider type, or the same type twice. A single-message envelope with no issue list. @@ -12506,6 +12739,7 @@ paths: schema: $ref: "#/components/schemas/InsightsFeedbackCreatedEnvelope" "401": *a1 + "403": *a9 "409": *a7 "422": description: The body failed validation. A single-message envelope (`Invalid feedback payload`) with no issue list — @@ -12549,7 +12783,7 @@ paths: schema: $ref: "#/components/schemas/CoachReadStripEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/narrative: @@ -12598,7 +12832,7 @@ paths: schema: $ref: "#/components/schemas/NarrativeEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/glp1-timeline: @@ -12635,7 +12869,7 @@ paths: schema: $ref: "#/components/schemas/Glp1TimelineEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/pulse/intraday: @@ -12671,7 +12905,7 @@ paths: schema: $ref: "#/components/schemas/IntradayPulseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/settings: @@ -12690,6 +12924,7 @@ paths: schema: $ref: "#/components/schemas/InsightsSettingsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -12715,6 +12950,7 @@ paths: schema: $ref: "#/components/schemas/InsightsSettingsUpdatedEnvelope" "401": *a1 + "403": *a9 "422": description: "`Invalid privacy mode` when `privacyMode` was a string outside the two accepted values, or `No changes` when the body carried nothing this endpoint writes. Single-message envelope with no issue list — this route @@ -12745,7 +12981,7 @@ paths: schema: $ref: "#/components/schemas/TargetsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/analytics/range: @@ -12787,6 +13023,7 @@ paths: schema: $ref: "#/components/schemas/AnalyticsRangeResponseEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/blood-pressure-status: @@ -12818,7 +13055,7 @@ paths: schema: $ref: "#/components/schemas/BloodPressureStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/pulse-status: @@ -12850,7 +13087,7 @@ paths: schema: $ref: "#/components/schemas/PulseStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/weight-status: @@ -12882,7 +13119,7 @@ paths: schema: $ref: "#/components/schemas/WeightStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/bmi-status: @@ -12914,7 +13151,7 @@ paths: schema: $ref: "#/components/schemas/BmiStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/mood-status: @@ -12946,7 +13183,7 @@ paths: schema: $ref: "#/components/schemas/MoodStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/medication-compliance-status: @@ -12979,7 +13216,7 @@ paths: schema: $ref: "#/components/schemas/MedicationComplianceStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/metric-status: @@ -13074,7 +13311,7 @@ paths: schema: $ref: "#/components/schemas/MetricStatusResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/biomarker-assessment: @@ -13119,7 +13356,7 @@ paths: schema: $ref: "#/components/schemas/BiomarkerAssessmentResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/derived: @@ -13231,7 +13468,7 @@ paths: schema: $ref: "#/components/schemas/DerivedMetricResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/derived/batch: @@ -13271,7 +13508,7 @@ paths: schema: $ref: "#/components/schemas/DerivedBatchResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/score-history: @@ -13321,7 +13558,7 @@ paths: schema: $ref: "#/components/schemas/ScoreHistoryResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/glp1-plateau: @@ -13341,7 +13578,7 @@ paths: schema: $ref: "#/components/schemas/InsightsGlp1PlateauResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/ecg: @@ -13363,7 +13600,7 @@ paths: schema: $ref: "#/components/schemas/EcgListResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -13401,6 +13638,7 @@ paths: schema: $ref: "#/components/schemas/EcgIngestCreatedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/insights/ecg/{id}: @@ -13440,7 +13678,7 @@ paths: schema: $ref: "#/components/schemas/EcgDetailResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No ECG recording with that id for the authenticated user (existence sealed — a foreign id is indistinguishable from a missing one). @@ -13472,7 +13710,7 @@ paths: schema: $ref: "#/components/schemas/RhythmEventsResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/correlations: @@ -13494,7 +13732,7 @@ paths: schema: $ref: "#/components/schemas/CorrelationDiscoveryResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/patterns: @@ -13512,7 +13750,7 @@ paths: schema: $ref: "#/components/schemas/CorrelationPatternListResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/patterns/{id}: @@ -13542,7 +13780,7 @@ paths: schema: $ref: "#/components/schemas/UpdateCorrelationPatternResponseEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: No current pattern with that id for this account. content: @@ -13567,7 +13805,7 @@ paths: schema: $ref: "#/components/schemas/InsightsHealthStatusEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/breathing-screening: @@ -13586,7 +13824,7 @@ paths: schema: $ref: "#/components/schemas/InsightsBreathingScreeningEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/insights/labs-changes: @@ -13605,7 +13843,7 @@ paths: schema: $ref: "#/components/schemas/InsightsLabsChangesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/mood/analytics: @@ -13628,7 +13866,7 @@ paths: schema: $ref: "#/components/schemas/MoodDailySeriesEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/mood/insights: @@ -13663,7 +13901,7 @@ paths: schema: $ref: "#/components/schemas/MoodAggregatesEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/mood/prognosis: @@ -13686,7 +13924,7 @@ paths: schema: $ref: "#/components/schemas/MoodPrognosisEnvelope" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/mood-entries/bulk: @@ -13715,6 +13953,7 @@ paths: schema: $ref: "#/components/schemas/BulkMoodResponseEnvelope" "401": *a1 + "403": *a9 "409": *a7 "422": *a4 "429": *a5 @@ -13743,6 +13982,7 @@ paths: schema: $ref: "#/components/schemas/MoodTagTreeEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/mood/tags/custom: @@ -13767,6 +14007,7 @@ paths: schema: $ref: "#/components/schemas/MoodCustomTagCreatedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/mood/tags/custom/{key}: @@ -13796,7 +14037,8 @@ paths: schema: $ref: "#/components/schemas/MoodCustomTagPatchEnvelope" "401": *a1 - "404": &a20 + "403": *a9 + "404": &a22 description: Not found / not owned by the caller. content: application/json: @@ -13831,7 +14073,8 @@ paths: schema: $ref: "#/components/schemas/MoodCustomTagDeleteEnvelope" "401": *a1 - "404": *a20 + "403": *a9 + "404": *a22 "422": *a4 "429": *a5 /api/mood/tags/{key}/hidden: @@ -13867,7 +14110,8 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "404": *a20 + "403": *a9 + "404": *a22 "422": *a4 "429": *a5 /api/mood/tags/groups: @@ -13891,6 +14135,7 @@ paths: schema: $ref: "#/components/schemas/MoodTagGroupCreatedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/mood/tags/groups/{key}: @@ -13920,7 +14165,8 @@ paths: schema: $ref: "#/components/schemas/MoodTagGroupPatchEnvelope" "401": *a1 - "404": *a20 + "403": *a9 + "404": *a22 "422": *a4 "429": *a5 delete: @@ -13951,7 +14197,8 @@ paths: schema: $ref: "#/components/schemas/MoodTagGroupDeleteEnvelope" "401": *a1 - "404": *a20 + "403": *a9 + "404": *a22 "422": *a4 "429": *a5 /api/mood/tags/layout: @@ -13969,6 +14216,7 @@ paths: schema: $ref: "#/components/schemas/MoodTagLayoutGetEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -13991,6 +14239,7 @@ paths: schema: $ref: "#/components/schemas/MoodTagLayoutPutEnvelope" "401": *a1 + "403": *a9 "409": description: Mood-tag layout changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -13999,7 +14248,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 /api/mood-entries: get: @@ -14083,7 +14332,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntryListEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -14116,8 +14365,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a11 - "409": &a21 + "403": *a12 + "409": &a23 description: A mood entry with the same `(date, moodLoggedAt)` instant already exists. `meta.errorCode` = `mood.duplicate_timestamp`. Only a request without `externalId` can answer this — with one, the write upserts in place. @@ -14126,7 +14375,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a12 + "429": *a13 /api/mood-entries/{id}: get: tags: @@ -14148,8 +14397,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a11 - "404": *a20 + "403": *a12 + "404": *a22 "422": *a4 "429": *a5 put: @@ -14183,9 +14432,9 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a11 - "404": *a20 - "409": *a21 + "403": *a12 + "404": *a22 + "409": *a23 "422": *a4 "429": *a5 delete: @@ -14210,8 +14459,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryDeleteEnvelope" "401": *a1 - "403": *a11 - "404": *a20 + "403": *a12 + "404": *a22 "422": *a4 "429": *a5 /api/mood-entries/restore: @@ -14240,7 +14489,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntriesRestoreEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -14269,7 +14518,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntriesBulkDeleteEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -14313,7 +14562,7 @@ paths: schema: $ref: "#/components/schemas/MoodLinkedContextEnvelope" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/settings/ntfy: @@ -14332,6 +14581,7 @@ paths: schema: $ref: "#/components/schemas/GetNtfySettings" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -14361,7 +14611,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutNtfySettingsResponse" - "400": &a22 + "400": &a24 description: Body is not parseable JSON. content: application/json: @@ -14369,18 +14619,20 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": - description: The operator switched ntfy off for this instance and the body asked to enable it. Nothing was stored. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator switched ntfy off + for this instance and the body asked to enable it. Nothing was stored." content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": &a23 + "413": &a25 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a24 + "415": &a26 description: Content-Type is not `application/json`. content: application/json: @@ -14411,6 +14663,7 @@ paths: schema: $ref: "#/components/schemas/GetTelegramSettings" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -14440,11 +14693,12 @@ paths: application/json: schema: $ref: "#/components/schemas/PutTelegramSettingsResponse" - "400": *a22 + "400": *a24 "401": *a1 "403": - description: The operator switched Telegram off for this instance and the body asked to enable it. No webhook was - registered and nothing was stored. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator switched Telegram + off for this instance and the body asked to enable it. No webhook was registered and nothing was stored." content: application/json: schema: @@ -14455,8 +14709,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": *a23 - "415": *a24 + "413": *a25 + "415": *a26 "422": description: Body matched neither accepted shape; or `enabled` is true without both a bot token and a chat id; or the deployment's `APP_URL` is not a public HTTPS host on port 80/88/443/8443; or Telegram refused the webhook @@ -14489,6 +14743,7 @@ paths: schema: $ref: "#/components/schemas/GetWebhookSettings" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -14510,10 +14765,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PutWebhookSettingsResponse" - "400": *a22 + "400": *a24 "401": *a1 - "413": *a23 - "415": *a24 + "403": *a9 + "413": *a25 + "415": *a26 "422": description: Body matched neither accepted shape, or `enabled` is true while no webhook URL is stored; or the URL is on a private network the operator has not listed in `NOTIFICATION_PRIVATE_ORIGINS` (`meta.errorCode` = @@ -14540,7 +14796,7 @@ paths: schema: $ref: "#/components/schemas/GetReminderThresholdsResponse" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/settings/global-services: @@ -14564,6 +14820,7 @@ paths: schema: $ref: "#/components/schemas/GlobalServiceAvailabilityEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/settings/account: @@ -14628,8 +14885,10 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "403": description: >- - The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal - happens before any second-factor proof is looked at. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The Bearer token carries a narrow + scope. Erasure needs a cookie session or a full-access token; the refusal happens before any second-factor + proof is looked at. Refused: the request was made while the session is acting on another account (`meta.errorCode` = @@ -14677,6 +14936,7 @@ paths: schema: $ref: "#/components/schemas/GetEmailChannelEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -14711,6 +14971,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/settings/email/test: @@ -14734,6 +14995,7 @@ paths: schema: $ref: "#/components/schemas/EmailTestEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "500": @@ -14771,6 +15033,7 @@ paths: schema: $ref: "#/components/schemas/NtfyTestEnvelope" "401": *a1 + "403": *a9 "422": description: The saved server is on a private network the operator has not approved (`meta.errorCode` = `private_origin_not_approved`), or is a link-local, metadata or unspecified address that no grant can open @@ -14820,6 +15083,7 @@ paths: schema: $ref: "#/components/schemas/TelegramTestEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/settings/webhook/test: @@ -14842,6 +15106,7 @@ paths: schema: $ref: "#/components/schemas/WebhookTestEnvelope" "401": *a1 + "403": *a9 "422": description: The saved URL is on a private network the operator has not approved (`meta.errorCode` = `private_origin_not_approved`), or is a link-local, metadata or unspecified address that no grant can open @@ -14933,8 +15198,10 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "403": description: >- - The Bearer token carries a narrow scope. Erasure needs a cookie session or a full-access token; the refusal - happens before any second-factor proof is looked at. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The Bearer token carries a narrow + scope. Erasure needs a cookie session or a full-access token; the refusal happens before any second-factor + proof is looked at. Refused: the request was made while the session is acting on another account (`meta.errorCode` = @@ -14976,6 +15243,7 @@ paths: schema: $ref: "#/components/schemas/ModuleListLayoutEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -14998,6 +15266,7 @@ paths: schema: $ref: "#/components/schemas/ModuleListLayoutSavedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/settings/preview-features: @@ -15014,6 +15283,7 @@ paths: schema: $ref: "#/components/schemas/PreviewFeaturesEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -15035,6 +15305,7 @@ paths: schema: $ref: "#/components/schemas/PreviewFeaturesSavedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/settings/privacy-summary: @@ -15053,6 +15324,7 @@ paths: schema: $ref: "#/components/schemas/PrivacySummaryEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/consent/ai: @@ -15091,6 +15363,7 @@ paths: schema: $ref: "#/components/schemas/ConsentValidationErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/consent/ai/web: @@ -15123,6 +15396,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/consent/ai/latest: @@ -15160,7 +15434,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentLatestForKindEnvelope" - $ref: "#/components/schemas/ConsentLatestByKindEnvelope" - "400": &a25 + "400": &a27 description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`, `ai_extraction`. Multi-issue envelope; nothing was read or revoked. The consent family answers 400 here where the rest of the API answers 422." @@ -15169,6 +15443,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -15220,8 +15495,9 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentRevokeForKindEnvelope" - $ref: "#/components/schemas/ConsentRevokeAllEnvelope" - "400": *a25 + "400": *a27 "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/import/health-connect-export: @@ -15260,6 +15536,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "409": description: An import for this account (Health Connect or Apple Health) is already queued or running. The upload was discarded. `errorCode` is `import.apple_health.busy` for either kind, `jobId` names the running job. @@ -15302,6 +15579,7 @@ paths: schema: $ref: "#/components/schemas/HealthConnectImportStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/import/csv: @@ -15354,6 +15632,7 @@ paths: schema: $ref: "#/components/schemas/CsvImportResponseEnvelope" "401": *a1 + "403": *a9 "413": description: CSV exceeds the 16 MB limit. content: @@ -15549,7 +15828,9 @@ paths: $ref: "#/components/schemas/LabsCsvImportEnvelope" "401": *a1 "403": - description: "`module.disabled`: the module the import writes to is switched off." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled`: the module the + import writes to is switched off." content: application/json: schema: @@ -15739,7 +16020,9 @@ paths: $ref: "#/components/schemas/MedicationsCsvImportEnvelope" "401": *a1 "403": - description: "`module.disabled`: the module the import writes to is switched off." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled`: the module the + import writes to is switched off." content: application/json: schema: @@ -15787,6 +16070,7 @@ paths: schema: $ref: "#/components/schemas/JsonImportStatsEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds the 16 MB limit. content: @@ -15856,6 +16140,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "409": description: An import for this account is already queued or running, and these bytes are not the ones it carries (the same bytes resolve to that job as a 202). `meta.errorCode` is `import.apple_health.busy` and `meta.jobId` @@ -15924,6 +16209,7 @@ paths: schema: $ref: "#/components/schemas/AppleHealthImportStatusEnvelope" "401": *a1 + "403": *a9 "404": description: No such job, or one the caller may not see. The two are indistinguishable on purpose. content: @@ -15952,7 +16238,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderListEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -15977,7 +16263,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderCreatedEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -16001,8 +16287,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderEnvelope" "401": *a1 - "403": *a11 - "404": &a26 + "403": *a12 + "404": &a28 description: Measurement reminder not found / not owned. content: application/json: @@ -16039,8 +16325,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderPatchEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 delete: @@ -16062,8 +16348,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderDeleteEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/satisfy: @@ -16087,8 +16373,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSatisfyEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/complete: @@ -16115,8 +16401,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderCompleteEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/skip: @@ -16145,8 +16431,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSkipEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/snooze: @@ -16179,8 +16465,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSnoozeEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/history: @@ -16220,8 +16506,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderHistoryEnvelope" "401": *a1 - "403": *a11 - "404": *a26 + "403": *a12 + "404": *a28 "422": *a4 "429": *a5 /api/labs: @@ -16299,7 +16585,7 @@ paths: schema: $ref: "#/components/schemas/ListLabResultsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -16326,7 +16612,7 @@ paths: schema: $ref: "#/components/schemas/CreateLabResultResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": description: "Nothing was written. `meta.errorCode` = `labs.create.invalid` when the body failed validation (every issue @@ -16341,7 +16627,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a12 + "429": *a13 /api/labs/overview: get: tags: @@ -16359,7 +16645,7 @@ paths: schema: $ref: "#/components/schemas/LabOverviewEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/labs/{id}: @@ -16382,8 +16668,8 @@ paths: schema: $ref: "#/components/schemas/GetLabResultResponse" "401": *a1 - "403": *a11 - "404": &a27 + "403": *a12 + "404": &a29 description: Lab result not found (or owned by another user). content: application/json: @@ -16417,8 +16703,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLabResultResponse" "401": *a1 - "403": *a11 - "404": *a27 + "403": *a12 + "404": *a29 "422": *a4 "429": *a5 delete: @@ -16440,8 +16726,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLabResultResponse" "401": *a1 - "403": *a11 - "404": *a27 + "403": *a12 + "404": *a29 "422": *a4 "429": *a5 /api/labs/restore: @@ -16467,7 +16753,7 @@ paths: schema: $ref: "#/components/schemas/RestoreLabResultsResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -16486,6 +16772,7 @@ paths: schema: $ref: "#/components/schemas/OcrCapabilityEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/labs/ocr/extract: @@ -16528,7 +16815,7 @@ paths: schema: $ref: "#/components/schemas/OcrTextExtractRequest" responses: - "202": &a37 + "202": &a39 description: Queued for the background worker. Every refusal the route can answer quickly (capability, provider, rate limit, budget, validation) has already been answered; anything that goes wrong from here is reported on the run. @@ -16552,15 +16839,16 @@ paths: $ref: "#/components/schemas/AiRunAcceptedEnvelope" "401": *a1 "403": - description: "AI refusal: the `labsOcr` capability is unavailable for this record. The envelope carries - `meta.errorCode`, `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). - `assistant.disabled.documentAi` — the operator turned off reading documents, or every AI feature (the master - switch). `module.disabled` with `meta.module = \"labs\"` — the module is off for this record. - `ai.record.notPermitted` — AI work is not admitted inside this record (a delegate acting in somebody else's - record). `consent.ai.required` — the provider this request would reach leaves the machine and the record - holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The question is - answered for the provider actually picked, so it can differ from the published capability when a text-mode - read takes a different provider than a vision read would." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `labsOcr` + capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and + `meta.reason` (one of `AiUnavailableReason`). `assistant.disabled.documentAi` — the operator turned off + reading documents, or every AI feature (the master switch). `module.disabled` with `meta.module = \"labs\"` + — the module is off for this record. `ai.record.notPermitted` — AI work is not admitted inside this record + (a delegate acting in somebody else's record). `consent.ai.required` — the provider this request would reach + leaves the machine and the record holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted + model is exempt. The question is answered for the provider actually picked, so it can differ from the + published capability when a text-mode read takes a different provider than a vision read would." content: application/json: schema: @@ -16599,6 +16887,7 @@ paths: schema: $ref: "#/components/schemas/OcrCommitEnvelope" "401": *a1 + "403": *a9 "409": *a7 "422": *a4 "429": *a5 @@ -16626,6 +16915,7 @@ paths: schema: $ref: "#/components/schemas/AiRunEnvelope" "401": *a1 + "403": *a9 "404": description: "`aiRuns.notFound`." content: @@ -16649,7 +16939,7 @@ paths: schema: $ref: "#/components/schemas/ListBiomarkersEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -16674,7 +16964,7 @@ paths: schema: $ref: "#/components/schemas/CreateBiomarkerResponse" "401": *a1 - "403": *a11 + "403": *a12 "409": description: A biomarker with this name already exists for the caller. content: @@ -16682,7 +16972,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a12 + "429": *a13 /api/biomarkers/{id}: get: tags: @@ -16705,8 +16995,8 @@ paths: schema: $ref: "#/components/schemas/GetBiomarkerResponse" "401": *a1 - "403": *a11 - "404": &a28 + "403": *a12 + "404": &a30 description: "Biomarker not found (or owned by another user). `meta.errorCode` = `labs.biomarker.notFound`. Since v1.43 a marker that was merged into another answers with `meta.mergedInto` naming the marker it went into: drop the cached marker and open that one." @@ -16747,8 +17037,8 @@ paths: schema: $ref: "#/components/schemas/UpdateBiomarkerResponse" "401": *a1 - "403": *a11 - "404": *a28 + "403": *a12 + "404": *a30 "409": description: "`labs.biomarker.duplicate`: a rename collides with another of the caller's markers. `labs.biomarker.alias.taken` (v1.43): one of `aliases` is already the name or an alias of another marker; @@ -16781,8 +17071,8 @@ paths: schema: $ref: "#/components/schemas/DeleteBiomarkerResponse" "401": *a1 - "403": *a11 - "404": *a28 + "403": *a12 + "404": *a30 "422": *a4 "429": *a5 /api/biomarkers/{id}/merge: @@ -16819,8 +17109,8 @@ paths: schema: $ref: "#/components/schemas/MergeBiomarkersResponse" "401": *a1 - "403": *a11 - "404": *a28 + "403": *a12 + "404": *a30 "409": *a7 "422": description: "`labs.biomarker.merge.invalid`: a malformed body, or the target among the sources (Zod issues in @@ -16831,7 +17121,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a12 + "429": *a13 /api/encounters: get: tags: @@ -16894,7 +17184,7 @@ paths: schema: $ref: "#/components/schemas/ListEncountersEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -16920,10 +17210,10 @@ paths: schema: $ref: "#/components/schemas/CreateEncounterEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 - "429": *a12 + "429": *a13 /api/encounters/procedures: get: tags: @@ -16954,7 +17244,7 @@ paths: schema: $ref: "#/components/schemas/ListProceduresEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/body-sites: @@ -16991,7 +17281,7 @@ paths: schema: $ref: "#/components/schemas/ListBodySitesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/encounters/suggest: @@ -17021,7 +17311,7 @@ paths: schema: $ref: "#/components/schemas/SuggestEncounterEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/encounters/{id}: @@ -17044,8 +17334,8 @@ paths: schema: $ref: "#/components/schemas/GetEncounterEnvelope" "401": *a1 - "403": *a11 - "404": &a29 + "403": *a12 + "404": &a31 description: Visit not found (or owned by another user). content: application/json: @@ -17078,8 +17368,8 @@ paths: schema: $ref: "#/components/schemas/UpdateEncounterEnvelope" "401": *a1 - "403": *a11 - "404": *a29 + "403": *a12 + "404": *a31 "422": *a4 "429": *a5 delete: @@ -17103,8 +17393,8 @@ paths: schema: $ref: "#/components/schemas/DeleteEncounterEnvelope" "401": *a1 - "403": *a11 - "404": *a29 + "403": *a12 + "404": *a31 "422": *a4 "429": *a5 /api/encounters/{id}/restore: @@ -17129,8 +17419,8 @@ paths: schema: $ref: "#/components/schemas/RestoreEncounterEnvelope" "401": *a1 - "403": *a11 - "404": *a29 + "403": *a12 + "404": *a31 "422": *a4 "429": *a5 /api/encounters/{id}/links: @@ -17161,7 +17451,8 @@ paths: schema: $ref: "#/components/schemas/LinkEncounterEnvelope" "401": *a1 - "404": *a29 + "403": *a9 + "404": *a31 "422": *a4 "429": *a5 delete: @@ -17190,7 +17481,8 @@ paths: schema: $ref: "#/components/schemas/UnlinkEncounterEnvelope" "401": *a1 - "404": *a29 + "403": *a9 + "404": *a31 "422": *a4 "429": *a5 /api/practitioners: @@ -17230,7 +17522,7 @@ paths: schema: $ref: "#/components/schemas/ListPractitionersEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -17254,7 +17546,7 @@ paths: schema: $ref: "#/components/schemas/CreatePractitionerEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -17278,8 +17570,8 @@ paths: schema: $ref: "#/components/schemas/GetPractitionerEnvelope" "401": *a1 - "403": *a11 - "404": &a30 + "403": *a12 + "404": &a32 description: Practitioner not found (or owned by another user). content: application/json: @@ -17312,8 +17604,8 @@ paths: schema: $ref: "#/components/schemas/UpdatePractitionerEnvelope" "401": *a1 - "403": *a11 - "404": *a30 + "403": *a12 + "404": *a32 "422": *a4 "429": *a5 delete: @@ -17337,8 +17629,8 @@ paths: schema: $ref: "#/components/schemas/DeletePractitionerEnvelope" "401": *a1 - "403": *a11 - "404": *a30 + "403": *a12 + "404": *a32 "422": *a4 "429": *a5 /api/practitioners/{id}/restore: @@ -17363,8 +17655,8 @@ paths: schema: $ref: "#/components/schemas/RestorePractitionerEnvelope" "401": *a1 - "403": *a11 - "404": *a30 + "403": *a12 + "404": *a32 "422": *a4 "429": *a5 /api/vaccinations/custom: @@ -17382,7 +17674,7 @@ paths: schema: $ref: "#/components/schemas/ListCustomVaccinesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -17407,8 +17699,8 @@ paths: schema: $ref: "#/components/schemas/CreateCustomVaccineEnvelope" "401": *a1 - "403": *a11 - "409": &a31 + "403": *a12 + "409": &a33 description: A live definition on the record already carries this name, compared without case (`vaccination.custom.name-taken`). content: @@ -17416,7 +17708,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a12 + "429": *a13 /api/vaccinations/custom/{id}: patch: tags: @@ -17444,14 +17736,14 @@ paths: schema: $ref: "#/components/schemas/UpdateCustomVaccineEnvelope" "401": *a1 - "403": *a11 - "404": &a32 + "403": *a12 + "404": &a34 description: No live definition with this id on the record (`vaccination.custom.not-found`). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a31 + "409": *a33 "422": *a4 "429": *a5 delete: @@ -17475,8 +17767,8 @@ paths: schema: $ref: "#/components/schemas/DeleteCustomVaccineEnvelope" "401": *a1 - "403": *a11 - "404": *a32 + "403": *a12 + "404": *a34 "422": *a4 "429": *a5 /api/vaccinations: @@ -17529,7 +17821,7 @@ paths: schema: $ref: "#/components/schemas/ListVaccinationsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -17556,7 +17848,7 @@ paths: schema: $ref: "#/components/schemas/CreateVaccinationEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: A practice, visit or own vaccine the body names is not the record's (`vaccination.practitioner-not-found`, `vaccination.encounter-not-found`, `vaccination.custom-vaccine-not-found`). @@ -17566,7 +17858,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "409": *a7 "422": *a4 - "429": *a12 + "429": *a13 /api/vaccinations/{id}: get: tags: @@ -17587,8 +17879,8 @@ paths: schema: $ref: "#/components/schemas/GetVaccinationEnvelope" "401": *a1 - "403": *a11 - "404": &a33 + "403": *a12 + "404": &a35 description: Vaccination not found (or owned by another user). content: application/json: @@ -17622,8 +17914,8 @@ paths: schema: $ref: "#/components/schemas/UpdateVaccinationEnvelope" "401": *a1 - "403": *a11 - "404": *a33 + "403": *a12 + "404": *a35 "422": *a4 "429": *a5 delete: @@ -17647,8 +17939,8 @@ paths: schema: $ref: "#/components/schemas/DeleteVaccinationEnvelope" "401": *a1 - "403": *a11 - "404": *a33 + "403": *a12 + "404": *a35 "422": *a4 "429": *a5 /api/vaccinations/{id}/restore: @@ -17672,8 +17964,8 @@ paths: schema: $ref: "#/components/schemas/RestoreVaccinationEnvelope" "401": *a1 - "403": *a11 - "404": *a33 + "403": *a12 + "404": *a35 "422": *a4 "429": *a5 /api/vaccinations/{id}/booster: @@ -17722,8 +18014,10 @@ paths: "401": *a1 "403": description: >- - Refused: the caller's grant does not open this record's measurements, and the booster reminder lives there - (`meta.errorCode` = `vaccination.booster-out-of-scope`). Nothing was written. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused: the caller's grant does + not open this record's measurements, and the booster reminder lives there (`meta.errorCode` = + `vaccination.booster-out-of-scope`). Nothing was written. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -17739,7 +18033,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": *a33 + "404": *a35 "422": *a4 "429": *a5 /api/vaccinations/suggest: @@ -17767,7 +18061,7 @@ paths: schema: $ref: "#/components/schemas/VaccinationSuggestEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/vaccinations/{id}/links: @@ -17799,7 +18093,8 @@ paths: schema: $ref: "#/components/schemas/LinkVaccinationEnvelope" "401": *a1 - "404": *a33 + "403": *a9 + "404": *a35 "422": *a4 "429": *a5 delete: @@ -17828,7 +18123,8 @@ paths: schema: $ref: "#/components/schemas/UnlinkVaccinationEnvelope" "401": *a1 - "404": *a33 + "403": *a9 + "404": *a35 "422": *a4 "429": *a5 /api/illness/episodes: @@ -17892,7 +18188,7 @@ paths: schema: $ref: "#/components/schemas/ListIllnessEpisodesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -17918,7 +18214,7 @@ paths: schema: $ref: "#/components/schemas/CreateIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -17942,8 +18238,8 @@ paths: schema: $ref: "#/components/schemas/GetIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 - "404": &a34 + "403": *a12 + "404": &a36 description: Illness episode not found (or owned by another user). content: application/json: @@ -17977,8 +18273,8 @@ paths: schema: $ref: "#/components/schemas/UpdateIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "422": *a4 "429": *a5 delete: @@ -18001,8 +18297,8 @@ paths: schema: $ref: "#/components/schemas/DeleteIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "422": *a4 "429": *a5 /api/illness/episodes/{id}/restore: @@ -18029,8 +18325,8 @@ paths: schema: $ref: "#/components/schemas/RestoreIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "409": *a7 "422": *a4 "429": *a5 @@ -18062,8 +18358,8 @@ paths: schema: $ref: "#/components/schemas/ResolveIllnessEpisodeEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "422": *a4 "429": *a5 /api/illness/episodes/{id}/day-logs: @@ -18113,8 +18409,8 @@ paths: schema: $ref: "#/components/schemas/GetIllnessDayLogEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "422": *a4 "429": *a5 post: @@ -18150,8 +18446,8 @@ paths: schema: $ref: "#/components/schemas/UpsertIllnessDayLogCreatedEnvelope" "401": *a1 - "403": *a11 - "404": *a34 + "403": *a12 + "404": *a36 "422": *a4 "429": *a5 /api/illness/episodes/{id}/correlation: @@ -18178,7 +18474,8 @@ paths: schema: $ref: "#/components/schemas/IllnessCorrelationEnvelope" "401": *a1 - "404": *a34 + "403": *a9 + "404": *a36 "422": *a4 "429": *a5 /api/illness/insights: @@ -18212,6 +18509,7 @@ paths: schema: $ref: "#/components/schemas/IllnessInsightsEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/mental-health/assessments: @@ -18251,7 +18549,7 @@ paths: schema: $ref: "#/components/schemas/ListMentalHealthAssessmentsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -18284,7 +18582,7 @@ paths: schema: $ref: "#/components/schemas/CreateMentalHealthAssessmentEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -18311,7 +18609,7 @@ paths: schema: $ref: "#/components/schemas/GetMentalHealthAssessmentEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/allergies: @@ -18344,7 +18642,7 @@ paths: schema: $ref: "#/components/schemas/ListAllergiesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -18369,10 +18667,10 @@ paths: schema: $ref: "#/components/schemas/CreateAllergyEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 - "429": *a12 + "429": *a13 /api/allergies/{id}: get: tags: @@ -18393,8 +18691,8 @@ paths: schema: $ref: "#/components/schemas/GetAllergyEnvelope" "401": *a1 - "403": *a11 - "404": &a35 + "403": *a12 + "404": &a37 description: Allergy not found (or owned by another user). content: application/json: @@ -18427,8 +18725,8 @@ paths: schema: $ref: "#/components/schemas/UpdateAllergyEnvelope" "401": *a1 - "403": *a11 - "404": *a35 + "403": *a12 + "404": *a37 "422": *a4 "429": *a5 delete: @@ -18451,8 +18749,8 @@ paths: schema: $ref: "#/components/schemas/DeleteAllergyEnvelope" "401": *a1 - "403": *a11 - "404": *a35 + "403": *a12 + "404": *a37 "422": *a4 "429": *a5 /api/family-history: @@ -18476,7 +18774,7 @@ paths: schema: $ref: "#/components/schemas/ListFamilyHistoryEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -18501,7 +18799,7 @@ paths: schema: $ref: "#/components/schemas/CreateFamilyHistoryEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -18525,8 +18823,8 @@ paths: schema: $ref: "#/components/schemas/GetFamilyHistoryEnvelope" "401": *a1 - "403": *a11 - "404": &a36 + "403": *a12 + "404": &a38 description: Family history entry not found (or owned by another user). content: application/json: @@ -18559,8 +18857,8 @@ paths: schema: $ref: "#/components/schemas/UpdateFamilyHistoryEnvelope" "401": *a1 - "403": *a11 - "404": *a36 + "403": *a12 + "404": *a38 "422": *a4 "429": *a5 delete: @@ -18583,8 +18881,8 @@ paths: schema: $ref: "#/components/schemas/DeleteFamilyHistoryEnvelope" "401": *a1 - "403": *a11 - "404": *a36 + "403": *a12 + "404": *a38 "422": *a4 "429": *a5 /api/documents/inbound: @@ -18699,7 +18997,7 @@ paths: schema: $ref: "#/components/schemas/InboundDocumentListEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -18866,6 +19164,7 @@ paths: schema: $ref: "#/components/schemas/DocumentUploadEnvelope" "401": *a1 + "403": *a9 "409": description: The same bytes are already stored under 20 other import source ids (`documents.inbound.sourceAliasLimit`). Nothing was stored or remembered. @@ -18935,6 +19234,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceLookupEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/capability: @@ -18960,6 +19260,7 @@ paths: schema: $ref: "#/components/schemas/DocumentAiCapabilityEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/usage: @@ -18982,7 +19283,7 @@ paths: schema: $ref: "#/components/schemas/DocumentUsageEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/documents/inbound/layout: @@ -19000,7 +19301,7 @@ paths: schema: $ref: "#/components/schemas/DocumentsLayoutEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 put: @@ -19023,6 +19324,7 @@ paths: schema: $ref: "#/components/schemas/DocumentsLayoutSavedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/bulk: @@ -19047,6 +19349,7 @@ paths: schema: $ref: "#/components/schemas/DocumentBulkEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/{id}: @@ -19069,7 +19372,7 @@ paths: schema: $ref: "#/components/schemas/InboundDocumentDetailEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 patch: @@ -19098,6 +19401,7 @@ paths: schema: $ref: "#/components/schemas/InboundDocumentUpdateEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 delete: @@ -19120,6 +19424,7 @@ paths: schema: $ref: "#/components/schemas/InboundDiscardEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/{id}/restore: @@ -19144,6 +19449,7 @@ paths: schema: $ref: "#/components/schemas/InboundRestoreEnvelope" "401": *a1 + "403": *a9 "409": description: "Not restorable: purged past the grace window, or an identical live document exists." content: @@ -19188,7 +19494,7 @@ paths: type: string format: binary "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/documents/inbound/{id}/thumbnail: @@ -19217,7 +19523,7 @@ paths: type: string format: binary "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/documents/inbound/{id}/extract: @@ -19249,7 +19555,7 @@ paths: required: true schema: type: string - - &a38 + - &a40 name: Prefer in: header required: false @@ -19274,18 +19580,20 @@ paths: application/json: schema: $ref: "#/components/schemas/InboundExtractEnvelope" - "202": *a37 + "202": *a39 "401": *a1 - "403": &a39 - description: "AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries - `meta.errorCode`, `meta.capability` and `meta.reason` (one of `AiUnavailableReason`). - `assistant.disabled.documentAi` — the operator turned off reading documents, or every AI feature (the master - switch). `module.disabled` with `meta.module = \"inboundDocuments\"` — the module is off for this record. - `ai.record.notPermitted` — AI work is not admitted inside this record (a delegate acting in somebody else's - record). `consent.ai.required` — the provider this request would reach leaves the machine and the record - holds no active `ai_extraction` or `ai_full` receipt; only a self-hosted model is exempt. The question is - answered for the provider actually picked, so it can differ from the published capability when a text-mode - read takes a different provider than a vision read would." + "403": &a41 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` + capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and + `meta.reason` (one of `AiUnavailableReason`). `assistant.disabled.documentAi` — the operator turned off + reading documents, or every AI feature (the master switch). `module.disabled` with `meta.module = + \"inboundDocuments\"` — the module is off for this record. `ai.record.notPermitted` — AI work is not + admitted inside this record (a delegate acting in somebody else's record). `consent.ai.required` — the + provider this request would reach leaves the machine and the record holds no active `ai_extraction` or + `ai_full` receipt; only a self-hosted model is exempt. The question is answered for the provider actually + picked, so it can differ from the published capability when a text-mode read takes a different provider than + a vision read would." content: application/json: schema: @@ -19300,7 +19608,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a40 + "503": &a42 description: "`ai.unavailable` — the inputs behind the AI capability could not be read, and the answer failed closed. Nothing was sent. Retry later." content: @@ -19330,7 +19638,7 @@ paths: required: true schema: type: string - - *a38 + - *a40 requestBody: required: false content: @@ -19366,12 +19674,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSuggestEnvelope" - "202": *a37 + "202": *a39 "401": *a1 - "403": *a39 + "403": *a41 "422": *a4 "429": *a5 - "503": *a40 + "503": *a42 /api/documents/inbound/{id}/summary: post: tags: @@ -19421,7 +19729,7 @@ paths: type: boolean default: false description: With `persist=true`, explicitly replace an existing stored summary. - - *a38 + - *a40 requestBody: required: false content: @@ -19457,12 +19765,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSummaryEnvelope" - "202": *a37 + "202": *a39 "401": *a1 - "403": *a39 + "403": *a41 "422": *a4 "429": *a5 - "503": *a40 + "503": *a42 /api/documents/inbound/{id}/index: post: tags: @@ -19488,7 +19796,7 @@ paths: required: true schema: type: string - - *a38 + - *a40 requestBody: required: false content: @@ -19524,12 +19832,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentIndexEnvelope" - "202": *a37 + "202": *a39 "401": *a1 - "403": *a39 + "403": *a41 "422": *a4 "429": *a5 - "503": *a40 + "503": *a42 /api/documents/inbound/{id}/chat: post: tags: @@ -19569,10 +19877,10 @@ paths: type: string description: "SSE frames: `data: \\n\\n`. See the operation description for the per-`type` frame shapes." "401": *a1 - "403": *a39 + "403": *a41 "422": *a4 "429": *a5 - "503": *a40 + "503": *a42 get: tags: - Documents @@ -19613,6 +19921,7 @@ paths: schema: $ref: "#/components/schemas/DocumentChatHistoryEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/reindex: @@ -19635,10 +19944,10 @@ paths: schema: $ref: "#/components/schemas/DocumentReindexEnvelope" "401": *a1 - "403": *a39 + "403": *a41 "422": *a4 "429": *a5 - "503": *a40 + "503": *a42 /api/documents/inbound/{id}/facts/{factId}: patch: tags: @@ -19671,6 +19980,7 @@ paths: schema: $ref: "#/components/schemas/ExtractedFactEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/documents/inbound/{id}/confirm: @@ -19702,6 +20012,7 @@ paths: schema: $ref: "#/components/schemas/InboundConfirmEnvelope" "401": *a1 + "403": *a9 "409": *a7 "422": *a4 "429": *a5 @@ -19720,7 +20031,7 @@ paths: schema: $ref: "#/components/schemas/ListCustomMetricsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 post: @@ -19745,7 +20056,8 @@ paths: schema: $ref: "#/components/schemas/CreateCustomMetricResponse" "401": *a1 - "409": &a42 + "403": *a9 + "409": &a44 description: A custom metric with this name already exists for the caller. content: application/json: @@ -19773,8 +20085,8 @@ paths: schema: $ref: "#/components/schemas/GetCustomMetricResponse" "401": *a1 - "403": *a11 - "404": &a41 + "403": *a12 + "404": &a43 description: Custom metric not found (or owned by another user). content: application/json: @@ -19808,8 +20120,9 @@ paths: schema: $ref: "#/components/schemas/UpdateCustomMetricResponse" "401": *a1 - "404": *a41 - "409": *a42 + "403": *a9 + "404": *a43 + "409": *a44 "422": *a4 "429": *a5 delete: @@ -19832,7 +20145,8 @@ paths: schema: $ref: "#/components/schemas/DeleteCustomMetricResponse" "401": *a1 - "404": *a41 + "403": *a9 + "404": *a43 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries: @@ -19875,8 +20189,8 @@ paths: schema: $ref: "#/components/schemas/ListCustomMetricEntriesEnvelope" "401": *a1 - "403": *a11 - "404": *a41 + "403": *a12 + "404": *a43 "422": *a4 "429": *a5 post: @@ -19905,10 +20219,11 @@ paths: schema: $ref: "#/components/schemas/CreateCustomMetricEntryResponse" "401": *a1 - "404": *a41 + "403": *a9 + "404": *a43 "409": *a7 "422": *a4 - "429": *a12 + "429": *a13 /api/custom-metrics/{id}/entries/{entryId}: patch: tags: @@ -19941,7 +20256,8 @@ paths: schema: $ref: "#/components/schemas/UpdateCustomMetricEntryResponse" "401": *a1 - "404": &a43 + "403": *a9 + "404": &a45 description: Custom metric entry not found (or owned by another user). content: application/json: @@ -19974,7 +20290,8 @@ paths: schema: $ref: "#/components/schemas/DeleteCustomMetricEntryResponse" "401": *a1 - "404": *a43 + "403": *a9 + "404": *a45 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries/restore: @@ -20005,6 +20322,7 @@ paths: schema: $ref: "#/components/schemas/RestoreCustomMetricEntriesResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/onboarding/answers: @@ -20045,6 +20363,7 @@ paths: schema: $ref: "#/components/schemas/OnboardingStateEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 16 KiB. content: @@ -20102,6 +20421,7 @@ paths: schema: $ref: "#/components/schemas/OnboardingStateEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 4 KiB. content: @@ -20183,6 +20503,7 @@ paths: schema: $ref: "#/components/schemas/OnboardingCompleteEnvelope" "401": *a1 + "403": *a9 "404": description: "`managedRecordId` names no managed profile the caller actively guards — missing, not a managed record, or not theirs; one answer for all three. `meta.errorCode` = `onboarding.complete.recordNotFound`. Nothing was @@ -20235,6 +20556,7 @@ paths: schema: $ref: "#/components/schemas/DisclaimerAckEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/onboarding/tour: @@ -20259,6 +20581,7 @@ paths: schema: $ref: "#/components/schemas/TourUpdateEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/nutrients/catalog: @@ -20276,11 +20599,12 @@ paths: schema: $ref: "#/components/schemas/NutrientCatalogEnvelope" "401": *a1 - "403": &a44 + "403": &a46 description: >- - The opt-in `nutrients` module is off for this account (errorCode `module.disabled`). Stop syncing and do not - retry; offer the enable-in-settings hint. Only run the HealthKit read-authorization flow while the module is - on. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is + off for this account (errorCode `module.disabled`). Stop syncing and do not retry; offer the + enable-in-settings hint. Only run the HealthKit read-authorization flow while the module is on. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -20389,9 +20713,10 @@ paths: $ref: "#/components/schemas/NutrientBatchEnvelope" "401": *a1 "403": - description: The opt-in `nutrients` module is off for this account (errorCode `module.disabled`). Stop syncing and do - not retry; offer the enable-in-settings hint. Only run the HealthKit read-authorization flow while the - module is on. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is + off for this account (errorCode `module.disabled`). Stop syncing and do not retry; offer the + enable-in-settings hint. Only run the HealthKit read-authorization flow while the module is on." content: application/json: schema: @@ -20423,7 +20748,7 @@ paths: schema: $ref: "#/components/schemas/NutrientOverviewEnvelope" "401": *a1 - "403": *a44 + "403": *a46 "422": *a4 "429": *a5 /api/nutrients/daily: @@ -20460,7 +20785,7 @@ paths: schema: $ref: "#/components/schemas/NutrientDailySeriesEnvelope" "401": *a1 - "403": *a44 + "403": *a46 "422": *a4 "429": *a5 /api/nutrients/water: @@ -20489,7 +20814,7 @@ paths: schema: $ref: "#/components/schemas/NutrientWaterWriteEnvelope" "401": *a1 - "403": *a44 + "403": *a46 "409": *a7 "422": *a4 "429": *a5 @@ -20508,6 +20833,7 @@ paths: schema: $ref: "#/components/schemas/HealthKitConfigEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 patch: @@ -20531,6 +20857,7 @@ paths: schema: $ref: "#/components/schemas/HealthKitConfigPatchEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/oura/sync: @@ -20548,6 +20875,7 @@ paths: schema: $ref: "#/components/schemas/OuraSyncEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "502": @@ -20573,6 +20901,7 @@ paths: schema: $ref: "#/components/schemas/PolarSyncEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "502": @@ -20600,6 +20929,7 @@ paths: schema: $ref: "#/components/schemas/StravaSyncEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 "502": @@ -20626,6 +20956,7 @@ paths: schema: $ref: "#/components/schemas/NightscoutSyncEnvelope" "401": *a1 + "403": *a9 "422": description: The configured Nightscout origin is private (RFC 1918 / loopback / link-local) and the server operator has not approved private-origin access for this deployment. @@ -20659,6 +20990,7 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthSyncStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/nightscout/connect: @@ -20691,6 +21023,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 16 KiB. content: @@ -20728,6 +21061,7 @@ paths: schema: $ref: "#/components/schemas/NightscoutStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/nightscout/test: @@ -20746,6 +21080,7 @@ paths: schema: $ref: "#/components/schemas/NightscoutTestEnvelope" "401": *a1 + "403": *a9 "422": description: No Nightscout instance is configured for this user. `meta.errorCode` = `not_configured`. content: @@ -20786,6 +21121,7 @@ paths: schema: $ref: "#/components/schemas/NightscoutDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Nightscout connection to clear. Note that this is NOT an idempotent no-op: a second disconnect answers 404 rather than 200." @@ -20810,6 +21146,7 @@ paths: schema: $ref: "#/components/schemas/WhoopCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -20838,6 +21175,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 64 KiB. content: @@ -20867,6 +21205,7 @@ paths: schema: $ref: "#/components/schemas/WhoopCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/whoop/status: @@ -20885,6 +21224,7 @@ paths: schema: $ref: "#/components/schemas/WhoopStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/whoop/disconnect: @@ -20905,6 +21245,7 @@ paths: schema: $ref: "#/components/schemas/WhoopDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No WHOOP connection to remove. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." content: @@ -20930,6 +21271,7 @@ paths: schema: $ref: "#/components/schemas/WithingsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/withings/disconnect: @@ -20948,6 +21290,7 @@ paths: schema: $ref: "#/components/schemas/WithingsDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Withings connection to remove. Note that this is NOT an idempotent no-op: a second disconnect answers 404 rather than 200." @@ -20971,6 +21314,7 @@ paths: schema: $ref: "#/components/schemas/WithingsCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -20991,26 +21335,27 @@ paths: application/json: schema: $ref: "#/components/schemas/WithingsCredentialsUpdateEnvelope" - "400": &a45 + "400": &a47 description: Body is not parseable JSON. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "413": &a46 + "403": *a9 + "413": &a48 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a47 + "415": &a49 description: Content-Type is not `application/json`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a48 + "422": &a50 description: "`clientId` and `clientSecret` are both required and each must be 1–200 characters. The envelope carries a single fixed message, not per-field issues." content: @@ -21033,6 +21378,7 @@ paths: schema: $ref: "#/components/schemas/WithingsCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/withings/sync: @@ -21063,6 +21409,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 64 KiB. content: @@ -21111,6 +21458,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 64 KiB. content: @@ -21155,6 +21503,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": description: More than 10 ticket mints in 60 s from this user. @@ -21176,6 +21525,7 @@ paths: schema: $ref: "#/components/schemas/FitbitCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -21196,11 +21546,12 @@ paths: application/json: schema: $ref: "#/components/schemas/FitbitCredentialsUpdateEnvelope" - "400": *a45 + "400": *a47 "401": *a1 - "413": *a46 - "415": *a47 - "422": *a48 + "403": *a9 + "413": *a48 + "415": *a49 + "422": *a50 "429": *a5 delete: tags: @@ -21217,6 +21568,7 @@ paths: schema: $ref: "#/components/schemas/FitbitCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fitbit/status: @@ -21234,6 +21586,7 @@ paths: schema: $ref: "#/components/schemas/FitbitStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/fitbit/disconnect: @@ -21253,6 +21606,7 @@ paths: schema: $ref: "#/components/schemas/FitbitDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Fitbit connection to remove. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." content: @@ -21289,6 +21643,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "410": description: Google turned the legacy Fitbit Web API off on 2026-10-30; nothing is fetched. `meta.errorCode` = `provider_retired`. The data already imported stays; Google Health reads the same devices. @@ -21330,6 +21685,7 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -21350,11 +21706,12 @@ paths: application/json: schema: $ref: "#/components/schemas/GoogleHealthCredentialsUpdateEnvelope" - "400": *a45 + "400": *a47 "401": *a1 - "413": *a46 - "415": *a47 - "422": *a48 + "403": *a9 + "413": *a48 + "415": *a49 + "422": *a50 "429": *a5 delete: tags: @@ -21371,6 +21728,7 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/google-health/status: @@ -21389,6 +21747,7 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/google-health/disconnect: @@ -21407,6 +21766,7 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Google Health connection to remove. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." @@ -21446,6 +21806,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 + "403": *a9 "409": description: Another sync of this account is already running (the history backfill after a reconnect, or the hourly poll), so none was started. `meta.errorCode` = `sync_in_progress`. `GET /api/google-health/sync/status` @@ -21495,6 +21856,7 @@ paths: schema: $ref: "#/components/schemas/OuraCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -21515,11 +21877,12 @@ paths: application/json: schema: $ref: "#/components/schemas/OuraCredentialsUpdateEnvelope" - "400": *a45 + "400": *a47 "401": *a1 - "413": *a46 - "415": *a47 - "422": *a48 + "403": *a9 + "413": *a48 + "415": *a49 + "422": *a50 "429": *a5 delete: tags: @@ -21536,6 +21899,7 @@ paths: schema: $ref: "#/components/schemas/OuraCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/oura/status: @@ -21554,6 +21918,7 @@ paths: schema: $ref: "#/components/schemas/OuraStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/oura/disconnect: @@ -21572,6 +21937,7 @@ paths: schema: $ref: "#/components/schemas/OuraDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Oura token stored. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." content: @@ -21595,21 +21961,22 @@ paths: schema: $ref: "#/components/schemas/OuraTestEnvelope" "401": *a1 - "422": &a49 + "403": *a9 + "422": &a51 description: No usable connection for this provider — nothing stored, or the stored grant could no longer be resolved into an access token. `meta.errorCode` = `not_configured`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a50 + "429": &a52 description: More than 5 requests in 60 s from this user. `meta.errorCode` = `rate_limited_self` — this is the local limiter, distinct from a `rate_limited` 502 which is the provider's. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": &a51 + "502": &a53 description: "The probe did not complete. `meta.errorCode` names the class: `credentials_rejected` (the provider answered 401/403 — the stored grant is gone), `rate_limited` (the PROVIDER answered 429, which is not this endpoint's own limiter), `upstream_error` (5xx), `timeout` (no answer within 5 s), `connection_failed` @@ -21633,6 +22000,7 @@ paths: schema: $ref: "#/components/schemas/PolarCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -21654,11 +22022,12 @@ paths: application/json: schema: $ref: "#/components/schemas/PolarCredentialsUpdateEnvelope" - "400": *a45 + "400": *a47 "401": *a1 - "413": *a46 - "415": *a47 - "422": *a48 + "403": *a9 + "413": *a48 + "415": *a49 + "422": *a50 "429": *a5 delete: tags: @@ -21675,6 +22044,7 @@ paths: schema: $ref: "#/components/schemas/PolarCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/polar/status: @@ -21693,6 +22063,7 @@ paths: schema: $ref: "#/components/schemas/PolarStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/polar/disconnect: @@ -21711,6 +22082,7 @@ paths: schema: $ref: "#/components/schemas/PolarDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Polar token stored. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." content: @@ -21734,9 +22106,10 @@ paths: schema: $ref: "#/components/schemas/PolarTestEnvelope" "401": *a1 - "422": *a49 - "429": *a50 - "502": *a51 + "403": *a9 + "422": *a51 + "429": *a52 + "502": *a53 /api/strava/credentials: get: tags: @@ -21752,6 +22125,7 @@ paths: schema: $ref: "#/components/schemas/StravaCredentialsStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -21772,11 +22146,12 @@ paths: application/json: schema: $ref: "#/components/schemas/StravaCredentialsUpdateEnvelope" - "400": *a45 + "400": *a47 "401": *a1 - "413": *a46 - "415": *a47 - "422": *a48 + "403": *a9 + "413": *a48 + "415": *a49 + "422": *a50 "429": *a5 delete: tags: @@ -21794,6 +22169,7 @@ paths: schema: $ref: "#/components/schemas/StravaCredentialsDeleteEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/strava/status: @@ -21812,6 +22188,7 @@ paths: schema: $ref: "#/components/schemas/StravaStatusEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/strava/disconnect: @@ -21831,6 +22208,7 @@ paths: schema: $ref: "#/components/schemas/StravaDisconnectEnvelope" "401": *a1 + "403": *a9 "404": description: "No Strava connection. NOT an idempotent no-op: a second disconnect answers 404 rather than 200." content: @@ -21854,9 +22232,10 @@ paths: schema: $ref: "#/components/schemas/StravaTestEnvelope" "401": *a1 - "422": *a49 - "429": *a50 - "502": *a51 + "403": *a9 + "422": *a51 + "429": *a52 + "502": *a53 /api/integrations/status: get: tags: @@ -21873,6 +22252,7 @@ paths: schema: $ref: "#/components/schemas/IntegrationStatusEnvelopeResponse" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/integrations/fitbit/resume: @@ -21891,8 +22271,9 @@ paths: schema: $ref: "#/components/schemas/FitbitResumeEnvelope" "401": *a1 + "403": *a9 "422": *a4 - "429": *a50 + "429": *a52 /api/integrations/fitbit/test: post: tags: @@ -21910,6 +22291,7 @@ paths: schema: $ref: "#/components/schemas/FitbitIntegrationTestEnvelope" "401": *a1 + "403": *a9 "410": description: Google turned the legacy Fitbit Web API off on 2026-10-30; nothing is probed. `meta.errorCode` = `provider_retired`. @@ -21917,9 +22299,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a49 - "429": *a50 - "502": *a51 + "422": *a51 + "429": *a52 + "502": *a53 /api/integrations/google-health/resume: post: tags: @@ -21935,8 +22317,9 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthResumeEnvelope" "401": *a1 + "403": *a9 "422": *a4 - "429": *a50 + "429": *a52 /api/integrations/google-health/test: post: tags: @@ -21963,15 +22346,16 @@ paths: schema: $ref: "#/components/schemas/GoogleHealthIntegrationTestEnvelope" "401": *a1 + "403": *a9 "413": description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a49 - "429": *a50 - "502": *a51 + "422": *a51 + "429": *a52 + "502": *a53 /api/integrations/whoop/resume: post: tags: @@ -21987,8 +22371,9 @@ paths: schema: $ref: "#/components/schemas/WhoopResumeEnvelope" "401": *a1 + "403": *a9 "422": *a4 - "429": *a50 + "429": *a52 /api/integrations/whoop/test: post: tags: @@ -22005,9 +22390,10 @@ paths: schema: $ref: "#/components/schemas/WhoopIntegrationTestEnvelope" "401": *a1 - "422": *a49 - "429": *a50 - "502": *a51 + "403": *a9 + "422": *a51 + "429": *a52 + "502": *a53 /api/integrations/withings/resume: post: tags: @@ -22024,8 +22410,9 @@ paths: schema: $ref: "#/components/schemas/WithingsResumeEnvelope" "401": *a1 + "403": *a9 "422": *a4 - "429": *a50 + "429": *a52 /api/integrations/withings/test: post: tags: @@ -22043,8 +22430,9 @@ paths: schema: $ref: "#/components/schemas/WithingsIntegrationTestEnvelope" "401": *a1 - "422": *a49 - "429": *a50 + "403": *a9 + "422": *a51 + "429": *a52 "502": description: "The probe did not complete. The shared codes apply (`credentials_rejected`, `rate_limited`, `upstream_error`, `timeout`, `connection_failed`) and this path adds one of its own: @@ -22077,7 +22465,7 @@ paths: schema: $ref: "#/components/schemas/DailyDigestEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/daily/digest/dismiss: @@ -22112,6 +22500,7 @@ paths: schema: $ref: "#/components/schemas/DismissPriorityItemEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/dashboard/chart-overlay-prefs: @@ -22146,6 +22535,7 @@ paths: schema: $ref: "#/components/schemas/ChartOverlayPrefsSavedEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/dashboard/summary: @@ -22168,7 +22558,7 @@ paths: schema: $ref: "#/components/schemas/DashboardSummaryEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/dashboard/widgets: @@ -22187,7 +22577,7 @@ paths: schema: $ref: "#/components/schemas/DashboardLayout" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 put: @@ -22213,6 +22603,7 @@ paths: schema: $ref: "#/components/schemas/DashboardLayoutSaved" "401": *a1 + "403": *a9 "409": description: Dashboard layout changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -22221,7 +22612,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a15 + "422": *a16 "429": *a5 delete: tags: @@ -22237,6 +22628,7 @@ paths: schema: $ref: "#/components/schemas/DashboardLayoutReset" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/managed-profiles/{id}/handover: @@ -22260,6 +22652,7 @@ paths: schema: $ref: "#/components/schemas/ManagedProfileHandoverStatusEnvelope" "401": *a1 + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`)." @@ -22300,9 +22693,11 @@ paths: $ref: "#/components/schemas/ManagedProfileHandoverEnvelope" "401": *a1 "403": - description: "Neither a password nor single sign-on could redeem a link on this instance (`meta.errorCode: - profile_claim.oidc_only_unsupported`). Since v1.43 a single-sign-on-only instance hands over through the - identity provider, so a working configuration never answers this." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Neither a password nor single + sign-on could redeem a link on this instance (`meta.errorCode: profile_claim.oidc_only_unsupported`). Since + v1.43 a single-sign-on-only instance hands over through the identity provider, so a working configuration + never answers this." content: application/json: schema: @@ -22342,6 +22737,7 @@ paths: schema: $ref: "#/components/schemas/RevokeManagedProfileHandoverEnvelope" "401": *a1 + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`)." @@ -22366,6 +22762,7 @@ paths: schema: $ref: "#/components/schemas/PendingHandoverDecisionEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 post: @@ -22389,6 +22786,7 @@ paths: schema: $ref: "#/components/schemas/HandoverDecisionEnvelope" "401": *a1 + "403": *a9 "409": description: "No decision is waiting (`meta.errorCode: managed_profile.handover.no_pending`)." content: @@ -22423,7 +22821,9 @@ paths: $ref: "#/components/schemas/AccountPayloadEnvelope" "401": *a1 "403": - description: "A selector header was attached to an actor surface (`meta.errorCode: sharing.not_permitted`)." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: A selector header was attached to + an actor surface (`meta.errorCode: sharing.not_permitted`)." content: application/json: schema: @@ -22450,11 +22850,13 @@ paths: schema: $ref: "#/components/schemas/RecordActivityEnvelope" "401": *a1 - "403": &a52 - description: Refused. `meta.errorCode` is `sharing.not_permitted` when the request was made while acting on another - account (grant management is never delegable), or `sharing.access.denied` when the caller may not act as the - account they named. The latter is byte-identical for an account that does not exist and one that granted - nothing — the refusal is not an enumeration oracle. + "403": &a54 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused. `meta.errorCode` is + `sharing.not_permitted` when the request was made while acting on another account (grant management is never + delegable), or `sharing.access.denied` when the caller may not act as the account they named. The latter is + byte-identical for an account that does not exist and one that granted nothing — the refusal is not an + enumeration oracle." content: application/json: schema: @@ -22475,7 +22877,7 @@ paths: schema: $ref: "#/components/schemas/AccountGrantListEnvelope" "401": *a1 - "403": *a52 + "403": *a54 "422": *a4 "429": *a5 post: @@ -22519,10 +22921,12 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "403": - description: Refused. `meta.errorCode` is `sharing.not_permitted` when the request was made while acting on another - account (grant management is never delegable), or `sharing.invite.manage_browser_only` when a Bearer caller - tried to offer MANAGE — the step-up that gates it is cookie-only by construction, so the native client - cannot mint it and should route the person to a browser rather than surface an error. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused. `meta.errorCode` is + `sharing.not_permitted` when the request was made while acting on another account (grant management is never + delegable), or `sharing.invite.manage_browser_only` when a Bearer caller tried to offer MANAGE — the step-up + that gates it is cookie-only by construction, so the native client cannot mint it and should route the + person to a browser rather than surface an error." content: application/json: schema: @@ -22577,7 +22981,7 @@ paths: schema: $ref: "#/components/schemas/AcceptedAccountGrantEnvelope" "401": *a1 - "403": *a52 + "403": *a54 "404": description: No such invitation for this account. content: @@ -22621,7 +23025,7 @@ paths: schema: $ref: "#/components/schemas/RevokedAccountGrantEnvelope" "401": *a1 - "403": *a52 + "403": *a54 "404": description: No such grant on this account's record. content: @@ -22662,7 +23066,7 @@ paths: schema: $ref: "#/components/schemas/RenouncedAccountGrantEnvelope" "401": *a1 - "403": *a52 + "403": *a54 "404": description: No such grant held by this account. content: @@ -22717,6 +23121,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "422": *a4 "429": description: "More than ten profiles from one caller within the hour (`meta.errorCode: http.rate_limited`, the status @@ -22750,6 +23155,7 @@ paths: schema: $ref: "#/components/schemas/ManagedProfileEnvelope" "401": *a1 + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`). The two are byte-identical, so the refusal is not an enumeration oracle." @@ -22794,6 +23200,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`). The two are byte-identical." @@ -22837,6 +23244,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`). The two are byte-identical, so the refusal is not an enumeration oracle — and @@ -22875,6 +23283,7 @@ paths: schema: $ref: "#/components/schemas/ManagedProfileGuardianListEnvelope" "401": *a1 + "403": *a9 "404": description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: managed_profile.not_found`). The two are byte-identical." @@ -22919,6 +23328,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "404": description: "Two refusals share this status, each with its own code. `meta.errorCode: sharing.invite.no_account`: no account on this instance carries that identifier — the same deliberate disclosure to an authenticated caller @@ -22989,6 +23399,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "403": *a9 "404": description: "`meta.errorCode: managed_profile.not_found`, for four situations the caller cannot tell apart: no such profile, no such grant on it, the caller is not one of its Guardians, or the target grant is the caller's @@ -23047,7 +23458,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a52 + "403": *a54 "409": description: "The record this browser session is on is not the record the request asserted. `meta.errorCode` is `sharing.session.changed`. It is not a refusal of access — the caller may well be entitled to both records — @@ -23127,7 +23538,9 @@ paths: $ref: "#/components/schemas/WebPushSubscribeEnvelope" "401": *a1 "403": - description: The operator switched Web Push off for this instance. Nothing was stored. + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator switched Web Push + off for this instance. Nothing was stored." content: application/json: schema: @@ -23166,6 +23579,7 @@ paths: schema: $ref: "#/components/schemas/WebPushUnsubscribeEnvelope" "401": *a1 + "403": *a9 "413": description: Request body exceeds 65536 bytes. content: @@ -23197,6 +23611,7 @@ paths: schema: $ref: "#/components/schemas/WebPushTestEnvelope" "401": *a1 + "403": *a9 "422": description: "Nothing to test against: the account has no Push subscription (`meta.errorCode` = `not_configured`), or the operator has configured no VAPID keys (`vapid_not_configured`)." @@ -23238,6 +23653,7 @@ paths: schema: $ref: "#/components/schemas/ApnsTestEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": description: More than 5 tests in a minute (`meta.errorCode` = `rate_limited_self`). @@ -23312,6 +23728,7 @@ paths: schema: $ref: "#/components/schemas/GetNotificationPreferences" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 put: @@ -23334,6 +23751,7 @@ paths: schema: $ref: "#/components/schemas/PutNotificationPreferenceResponse" "401": *a1 + "403": *a9 "404": description: No such channel for this caller. content: @@ -23369,6 +23787,7 @@ paths: schema: $ref: "#/components/schemas/GetNotificationStatus" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 post: @@ -23393,6 +23812,7 @@ paths: schema: $ref: "#/components/schemas/ReEnableNotificationChannelResponse" "401": *a1 + "403": *a9 "404": description: No such channel for this caller. content: @@ -23427,10 +23847,11 @@ paths: schema: $ref: "#/components/schemas/McpConnectionListEnvelope" "401": *a1 - "403": &a53 - description: The operator has disabled the API instance-wide. Every route in this module refuses while that setting is - off, for a cookie session as well as for a Bearer token; it is not a per-user permission and retrying will - not help. + "403": &a55 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API + instance-wide. Every route in this module refuses while that setting is off, for a cookie session as well as + for a Bearer token; it is not a per-user permission and retrying will not help." content: application/json: schema: @@ -23460,7 +23881,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionRevokeEnvelope" "401": *a1 - "403": *a53 + "403": *a55 "404": description: No live connection with this id for this caller — also the answer for an already-revoked one, so a repeat revoke is 404 rather than an idempotent 200. @@ -23487,7 +23908,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenListEnvelope" "401": *a1 - "403": *a53 + "403": *a55 "422": *a4 "429": *a5 post: @@ -23520,7 +23941,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a53 + "403": *a55 "413": description: Body exceeds 16 KiB. content: @@ -23563,7 +23984,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenRevokeEnvelope" "401": *a1 - "403": *a53 + "403": *a55 "404": description: No `health:read` token with this id for this caller. An already-revoked token is still found, so a repeat revoke answers 200. @@ -23590,7 +24011,7 @@ paths: schema: $ref: "#/components/schemas/RecordSettingsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/record-settings/integrations: @@ -23609,7 +24030,7 @@ paths: schema: $ref: "#/components/schemas/RecordSettingsIntegrationsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/record-settings/integrations/{provider}/connect: @@ -23625,7 +24046,7 @@ paths: record (audited). Fitbit is not offered: its classic API is retired. Cookie session, while acting on a managed record the caller guards." parameters: - - &a54 + - &a56 name: provider in: path required: true @@ -23647,7 +24068,7 @@ paths: schema: $ref: "#/components/schemas/ManagedConnectStartEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: The path named a provider outside the closed set. Answered before the caller is authenticated. content: @@ -23672,7 +24093,7 @@ paths: Audited against the record with the Guardian as the actor. Cookie session or Bearer, while acting on a managed record the caller guards." parameters: - - *a54 + - *a56 responses: "200": description: The provider is disconnected. @@ -23681,7 +24102,7 @@ paths: schema: $ref: "#/components/schemas/ManagedDisconnectEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: The path named a provider outside the closed set, or the record has no connection to it. content: @@ -23698,7 +24119,7 @@ paths: description: Returns the named family's resolved settings for the record the session is acting on. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - &a55 + - &a57 name: family in: path required: true @@ -23722,7 +24143,7 @@ paths: schema: $ref: "#/components/schemas/GetRecordSettingsFamilyEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: Either the path named a family outside the closed set, or the record row itself is gone. The two are not distinguished, and the first is answered before the caller is authenticated at all. @@ -23742,7 +24163,7 @@ paths: ride the audit row. Body capped at 64 KiB. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - *a55 + - *a57 requestBody: required: true content: @@ -23763,7 +24184,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: The path named a family outside the closed set. Answered before authentication. content: @@ -23877,7 +24298,7 @@ paths: schema: $ref: "#/components/schemas/ListPersonalRecordsEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": description: "`metricType` or `limit` carried an unacceptable value. `meta.errorCode` = `personal_records.invalid_query`, with the offending path under `details.issues`." @@ -23930,7 +24351,7 @@ paths: schema: $ref: "#/components/schemas/AchievementsEnvelope" "401": *a1 - "403": *a13 + "403": *a14 "422": *a4 "429": *a5 /api/environment/preferences: @@ -23956,7 +24377,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentPreferencesEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/environment: @@ -23974,7 +24395,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentOverviewEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/environment/home: @@ -24011,7 +24432,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentHomeEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "413": description: Body exceeds 4 KiB. content: @@ -24071,7 +24492,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentBackfillEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "409": description: No home location is set, so there is nothing to resolve the days against (`meta.errorCode` = `environment.no_home`), or a backfill for this account is already queued in the current twenty-minute slot @@ -24131,7 +24552,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentGeocodeEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": description: "`q` was missing, empty after trimming, or longer than 120 characters. `meta.errorCode` = `environment.invalid`." @@ -24170,7 +24591,7 @@ paths: schema: $ref: "#/components/schemas/CreateEnvironmentTravelEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "413": description: Body exceeds 4 KiB. content: @@ -24223,7 +24644,7 @@ paths: schema: $ref: "#/components/schemas/DeleteEnvironmentTravelEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "404": description: No such override for this account. `meta.errorCode` = `environment.travel.not_found`. Also the answer for an id owned by somebody else — existence is not leaked. @@ -24369,13 +24790,13 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupDocument" - "403": &a56 + "403": &a58 description: Caller is not an admin. `requireAdmin()` is cookie-only, so a Bearer token lands here whatever its scope. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": &a57 + "404": &a59 description: No stored copy with this id. content: application/json: @@ -24449,8 +24870,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "403": *a56 - "404": *a57 + "403": *a58 + "404": *a59 "409": description: A restore of the same account is already queued or running (`meta.errorCode` = `backup.restore.active`, `meta.jobId` names it), the stored copy was replaced by a newer one while it was being checked @@ -24494,7 +24915,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobListResponse" - "403": *a56 + "403": *a58 /api/admin/backups/restores/{jobId}: get: tags: @@ -24517,7 +24938,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobResponse" - "403": *a56 + "403": *a58 "404": description: No restore job with this id. content: @@ -24536,7 +24957,7 @@ paths: request leaves the server. Needs neither the Documents module nor the list: with the list unset, `available` is false and any connection still stored is listed (with `originAllowed: false`) so it can be removed. Cookie session only; not delegable." - security: &a58 + security: &a60 - cookieAuth: [] responses: "200": @@ -24546,10 +24967,12 @@ paths: schema: $ref: "#/components/schemas/DocumentSourcesStatusEnvelope" "401": *a1 - "403": &a59 - description: "Refused before any request to the source: `documents.sources.browserOnly` for a Bearer caller, - `sharing.not_permitted` while acting on another person's record (a delegate or guardian never uses the - owner's archive), or `module.disabled` when the Documents module is off." + "403": &a61 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Refused before any request to the + source: `documents.sources.browserOnly` for a Bearer caller, `sharing.not_permitted` while acting on another + person's record (a delegate or guardian never uses the owner's archive), or `module.disabled` when the + Documents module is off." content: application/json: schema: @@ -24569,9 +24992,9 @@ paths: `documents.sources.tokenRequired`), so a stored token is never sent to an address it was not saved for. A Papra organization id is letters, digits, `_` and `-`. The token is stored encrypted (AES-256-GCM) and never returned. Ten saves and tests a minute per person. Cookie session only; not delegable." - security: *a58 + security: *a60 parameters: - - &a60 + - &a62 name: system in: path required: true @@ -24610,8 +25033,8 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceConnectionEnvelope" "401": *a1 - "403": *a59 - "404": &a61 + "403": *a61 + "404": &a63 description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. `documents.sources.notFound`: the source has no such document, or (Papra) no such organization, including @@ -24630,7 +25053,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": &a62 + "502": &a64 description: "The source could not answer usefully, with the reason in `meta.errorCode` and, when the source answered at all, its HTTP status in `meta.upstreamStatus` (its body is never passed on): `documents.sources.unreachable` (no connection, timeout, or a destination refused at dial time), `documents.sources.redirected` (the source @@ -24649,9 +25072,9 @@ paths: description: Deletes the caller's connection for this system, and its token with it. Works even after the operator removed the origin from the list, and with the Documents module switched off. `disconnected` is false when there was nothing to delete. Cookie session only; not delegable. - security: *a58 + security: *a60 parameters: - - *a60 + - *a62 responses: "200": description: Removed, or there was nothing to remove. @@ -24660,7 +25083,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceDisconnectEnvelope" "401": *a1 - "403": *a59 + "403": *a61 "422": *a4 "429": *a5 /api/documents/sources/{system}/test: @@ -24671,9 +25094,9 @@ paths: description: One request to the source with the stored token. Answers `ok` with the round-trip time and stamps `lastVerifiedAt`, or the reason in `meta.errorCode`. Shares the save's allowance of ten a minute. Cookie session only; not delegable. - security: *a58 + security: *a60 parameters: - - *a60 + - *a62 responses: "200": description: The source answered. @@ -24682,9 +25105,9 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTestEnvelope" "401": *a1 - "403": *a59 - "404": *a61 - "422": &a63 + "403": *a61 + "404": *a63 + "422": &a65 description: "`documents.sources.originNotAllowed`: the base address's origin is not on the operator's list (checked on every request, so a connection saved earlier is cut off once its origin is removed), or another validation failure." @@ -24693,7 +25116,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": *a62 + "502": *a64 /api/documents/sources/{system}/tags: get: tags: @@ -24701,9 +25124,9 @@ paths: summary: Tags of a connected archive description: The source's tags for the picker's filter, the first 250 by name, in the source's own ids. Shares the search allowance of sixty a minute. Cookie session only; not delegable. - security: *a58 + security: *a60 parameters: - - *a60 + - *a62 responses: "200": description: The tags. @@ -24712,11 +25135,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTagsEnvelope" "401": *a1 - "403": *a59 - "404": *a61 - "422": *a63 + "403": *a61 + "404": *a63 + "422": *a65 "429": *a5 - "502": *a62 + "502": *a64 /api/documents/sources/{system}/search: get: tags: @@ -24730,9 +25153,9 @@ paths: under that source key: `new`, `imported` with the vault document's `documentId`, or `deleted` (deleted in HealthLog; importing it again stores nothing). Nothing is stored. Sixty searches a minute, shared with the tag read. Cookie session only; not delegable." - security: *a58 + security: *a60 parameters: - - *a60 + - *a62 - name: q in: query required: false @@ -24773,11 +25196,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceSearchEnvelope" "401": *a1 - "403": *a59 - "404": *a61 - "422": *a63 + "403": *a61 + "404": *a63 + "422": *a65 "429": *a5 - "502": *a62 + "502": *a64 /api/documents/sources/{system}/import: post: tags: @@ -24796,9 +25219,9 @@ paths: fetched), including a duplicate. `kind` sets the document type (default OTHER). Automatic AI reading follows the person's own setting. Counts against the person's upload allowance (60 an hour) and storage quota. 201 for a new document, 200 otherwise. Cookie session only; not delegable." - security: *a58 + security: *a60 parameters: - - *a60 + - *a62 requestBody: required: true content: @@ -24856,7 +25279,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceImportEnvelope" "401": *a1 - "403": *a59 + "403": *a61 "404": description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. @@ -24887,7 +25310,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a63 + "422": *a65 "429": description: "`documents.inbound.rateLimited` (the person's hourly upload allowance, with the rate-limit headers) or `documents.inbound.uploadBusy` (too many uploads in progress; `Retry-After: 1`)." @@ -24895,7 +25318,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": *a62 + "502": *a64 /api/symptoms/definitions: get: tags: @@ -24919,10 +25342,11 @@ paths: schema: $ref: "#/components/schemas/ListSymptomDefinitionsEnvelope" "401": *a1 - "403": &a64 + "403": &a66 description: >- - `illness.disabled`: the record has the illness module switched off (or the operator turned it off - server-wide); symptoms ride that module. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: `illness.disabled`: the record + has the illness module switched off (or the operator turned it off server-wide); symptoms ride that module. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -24962,7 +25386,7 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomDefinitionEnvelope" "401": *a1 - "403": *a64 + "403": *a66 "409": *a7 "422": *a4 "429": *a5 @@ -24992,8 +25416,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomDefinitionEnvelope" "401": *a1 - "403": *a64 - "404": &a65 + "403": *a66 + "404": &a67 description: "`symptoms.definition.notFound`: no definition with this id on the record." content: application/json: @@ -25027,8 +25451,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomDefinitionEnvelope" "401": *a1 - "403": *a64 - "404": *a65 + "403": *a66 + "404": *a67 "422": *a4 "429": *a5 /api/symptoms/events: @@ -25077,7 +25501,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomEventsEnvelope" "401": *a1 - "403": *a64 + "403": *a66 "422": *a4 "429": *a5 post: @@ -25103,8 +25527,8 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomEventEnvelope" "401": *a1 - "403": *a64 - "404": *a65 + "403": *a66 + "404": *a67 "409": *a7 "422": *a4 "429": *a5 @@ -25134,8 +25558,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomEventEnvelope" "401": *a1 - "403": *a64 - "404": &a66 + "403": *a66 + "404": &a68 description: "`symptoms.event.notFound`: no occurrence with this id on the record." content: application/json: @@ -25162,8 +25586,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomEventEnvelope" "401": *a1 - "403": *a64 - "404": *a66 + "403": *a66 + "404": *a68 "422": *a4 "429": *a5 /api/day/index: @@ -25196,7 +25620,7 @@ paths: schema: $ref: "#/components/schemas/DayIndexEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/day/notable: @@ -25231,7 +25655,7 @@ paths: schema: $ref: "#/components/schemas/DayNotableWindowEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/day/{date}: @@ -25261,7 +25685,7 @@ paths: schema: $ref: "#/components/schemas/DayEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/timeline: @@ -25314,10 +25738,12 @@ paths: schema: $ref: "#/components/schemas/TimelineEnvelope" "401": *a1 - "403": &a67 + "403": &a69 description: >- - `module.disabled` with `meta.module = timeline`: the record has the timeline switched off (it is opt-in), or - the operator turned it off server-wide. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled` with + `meta.module = timeline`: the record has the timeline switched off (it is opt-in), or the operator turned it + off server-wide. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -25351,7 +25777,7 @@ paths: schema: $ref: "#/components/schemas/TimelineReadinessEnvelope" "401": *a1 - "403": *a67 + "403": *a69 "422": *a4 "429": *a5 /api/life-events: @@ -25369,10 +25795,12 @@ paths: schema: $ref: "#/components/schemas/LifeEventListEnvelope" "401": *a1 - "403": &a68 + "403": &a70 description: >- - Owner-only in v1.42: no share reaches life events, not a `profile` share, a whole-record share or MANAGE, - and not a guardian acting for a managed profile. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Owner-only in v1.42: no share + reaches life events, not a `profile` share, a whole-record share or MANAGE, and not a guardian acting for a + managed profile. Refused: the request was made while the session is acting on another account (`meta.errorCode` = @@ -25409,10 +25837,10 @@ paths: schema: $ref: "#/components/schemas/CreateLifeEventEnvelope" "401": *a1 - "403": *a68 + "403": *a70 "409": *a7 "422": *a4 - "429": *a12 + "429": *a13 /api/life-events/{id}: patch: tags: @@ -25440,8 +25868,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLifeEventEnvelope" "401": *a1 - "403": *a68 - "404": &a69 + "403": *a70 + "404": &a71 description: No such life event on this account. content: application/json: @@ -25468,8 +25896,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLifeEventEnvelope" "401": *a1 - "403": *a68 - "404": *a69 + "403": *a70 + "404": *a71 "422": *a4 "429": *a5 /api/search: @@ -25515,7 +25943,7 @@ paths: schema: $ref: "#/components/schemas/SearchEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/auth/me/ai-usage: @@ -25540,6 +25968,7 @@ paths: schema: $ref: "#/components/schemas/PutAiUsageResponse" "401": *a1 + "403": *a9 "422": description: "`ai.usage.invalid` (body) or `ai.usage.aboveCeiling` (`meta.maxLevel` names the ceiling)." content: @@ -25582,8 +26011,9 @@ paths: "401": *a1 "403": description: >- - The opt-in `nutrients` module is off for this account: 403 `module.disabled` with `meta.module: - "nutrients"`. + `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is + off for this account: 403 `module.disabled` with `meta.module: "nutrients"`. Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). @@ -25642,16 +26072,17 @@ paths: required: - file responses: - "202": *a37 + "202": *a39 "401": *a1 "403": - description: "The opt-in `nutrients` module is off for this account: 403 `module.disabled` with `meta.module: - \"nutrients\"`. The `supplementLabel` AI capability is unavailable for this record. `meta.capability` = - `supplementLabel` and `meta.reason` says which layer refused: the operator's switch - (`assistant.disabled.documentAi`, or `assistant.disabled.enabled` for the master switch), an owning module - or the person's own AI opt-out (`module.disabled` with `meta.module`), a delegate inside somebody else's - record (`ai.record.notPermitted`), or a missing consent receipt (`consent.ai.required`). Nothing was - generated, read from a stored model answer, or enqueued." + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is + off for this account: 403 `module.disabled` with `meta.module: \"nutrients\"`. The `supplementLabel` AI + capability is unavailable for this record. `meta.capability` = `supplementLabel` and `meta.reason` says + which layer refused: the operator's switch (`assistant.disabled.documentAi`, or `assistant.disabled.enabled` + for the master switch), an owning module or the person's own AI opt-out (`module.disabled` with + `meta.module`), a delegate inside somebody else's record (`ai.record.notPermitted`), or a missing consent + receipt (`consent.ai.required`). Nothing was generated, read from a stored model answer, or enqueued." content: application/json: schema: @@ -25677,7 +26108,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a18 + "503": *a20 /api/anamnesis/life-circumstances: get: tags: @@ -25692,7 +26123,7 @@ paths: schema: $ref: "#/components/schemas/LifeCircumstancesEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "422": *a4 "429": *a5 /api/anamnesis/life-circumstances/ai-sections: @@ -25716,6 +26147,7 @@ paths: schema: $ref: "#/components/schemas/LifeAiSectionsEnvelope" "401": *a1 + "403": *a9 "409": description: "`about_me_conflict`." content: @@ -25745,6 +26177,7 @@ paths: schema: $ref: "#/components/schemas/LifeConfirmEnvelope" "401": *a1 + "403": *a9 "422": *a4 "429": *a5 /api/anamnesis/audit-c: @@ -25779,7 +26212,7 @@ paths: schema: $ref: "#/components/schemas/AuditCCreatedEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "409": *a7 "422": *a4 "429": *a5 @@ -25803,7 +26236,7 @@ paths: schema: $ref: "#/components/schemas/AuditCRemovedEnvelope" "401": *a1 - "403": *a11 + "403": *a12 "404": description: "`anamnesis.auditC.notFound`." content: @@ -25834,7 +26267,7 @@ paths: schema: $ref: "#/components/schemas/DawarichConnectEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/dawarich/status: @@ -25850,7 +26283,7 @@ paths: schema: $ref: "#/components/schemas/DawarichStatusEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/dawarich/places: @@ -25874,7 +26307,7 @@ paths: schema: $ref: "#/components/schemas/DawarichPlacesEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "409": description: "`dawarich.not_connected`." content: @@ -25912,7 +26345,7 @@ paths: schema: $ref: "#/components/schemas/DawarichSettingsEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "409": description: "`dawarich.not_connected`." content: @@ -25944,7 +26377,7 @@ paths: schema: $ref: "#/components/schemas/DawarichProbeEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 "502": @@ -25980,7 +26413,7 @@ paths: schema: $ref: "#/components/schemas/DawarichSyncEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "409": description: "`dawarich.not_connected`." content: @@ -26015,7 +26448,7 @@ paths: schema: $ref: "#/components/schemas/DawarichDisconnectEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/environment/travel/suggestions: @@ -26033,7 +26466,7 @@ paths: schema: $ref: "#/components/schemas/TravelSuggestionsEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "422": *a4 "429": *a5 /api/environment/travel/suggestions/{id}/dismiss: @@ -26056,7 +26489,7 @@ paths: schema: $ref: "#/components/schemas/TravelSuggestionDismissEnvelope" "401": *a1 - "403": *a19 + "403": *a21 "404": description: "`environment.travel_suggestion.not_found`." content: @@ -26093,6 +26526,7 @@ paths: schema: $ref: "#/components/schemas/MedicationLinkResponse" "401": *a1 + "403": *a9 "404": description: The medication was not found. content: @@ -26128,6 +26562,7 @@ paths: schema: $ref: "#/components/schemas/MedicationUnlinkResponse" "401": *a1 + "403": *a9 "404": description: The medication was not found. content: @@ -26164,6 +26599,7 @@ paths: schema: $ref: "#/components/schemas/LabResultLinkResponse" "401": *a1 + "403": *a9 "404": description: The lab result was not found. content: @@ -26199,6 +26635,7 @@ paths: schema: $ref: "#/components/schemas/LabResultUnlinkResponse" "401": *a1 + "403": *a9 "404": description: The lab result was not found. content: diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index fddd0c974..741528ed9 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -89,10 +89,12 @@ Every call is also a contract check. The status must be one the registry (`src/lib/openapi/routes/`) declares for that path and method, a JSON body must parse with the registered schema, and a key the schema does not declare fails the call: a client generated from the contract cannot see that field, so -nobody promised to keep it. The one generic answer accepted without its own -entry is the scope gate's 403 `auth.scope.insufficient`, which the shared 401 -documents for every authenticated operation. A test that calls an unregistered -route on purpose passes `contract: { skip: "" }`. +nobody promised to keep it. There is no generic answer the kit accepts on an +operation's behalf: the scope gate's 403 `auth.scope.insufficient` is declared +on every operation that passes the gate (`withScopeRefusal` in +`src/lib/openapi/routes/shared.ts`), so it is checked like any other status. A +test that calls an unregistered route on purpose passes +`contract: { skip: "" }`. `tests/integration/routes/kit-contract.test.ts` proves the check can fail. Assert what a person or a client would notice: the stored row, the response, diff --git a/src/lib/openapi/__tests__/gate-refusal-publication.test.ts b/src/lib/openapi/__tests__/gate-refusal-publication.test.ts new file mode 100644 index 000000000..3a70c7c04 --- /dev/null +++ b/src/lib/openapi/__tests__/gate-refusal-publication.test.ts @@ -0,0 +1,134 @@ +/** + * The generic auth gate's 403, declared on every operation that passes it. + * + * `requireAuth` answers 403 `auth.scope.insufficient` to a narrow Bearer token + * on a route that names no scope it holds, and `requireAdmin` answers 403 + * `auth.admin.required` to a session that is not an administrator's. Both run + * before a route's own body, so every operation behind them can send the + * status; `withScopeRefusal` declares it on the assembled table. These checks + * hold the published document to that, and hold the helper to not trampling + * the 403s operations already publish for reasons of their own. + */ +import { describe, expect, it } from "vitest"; + +import { openApiPaths } from "../routes"; +import { + adminRefusalResponse, + errorEnvelope, + scopeRefusalResponse, + stdResponses, + withScopeRefusal, +} from "../routes/shared"; + +const METHODS = ["get", "put", "post", "patch", "delete"] as const; + +interface Op { + security?: unknown[]; + responses?: Record; +} + +function operations(): Array<{ path: string; method: string; op: Op }> { + const out: Array<{ path: string; method: string; op: Op }> = []; + for (const [path, item] of Object.entries(openApiPaths)) { + for (const method of METHODS) { + const op = (item as Record)[method]; + if (op?.responses) out.push({ path, method, op }); + } + } + return out; +} + +const URL_CREDENTIAL_OR_OWN_GATE = new Set([ + "/api/c/{token}/unlock", + "/api/notifications/action/{token}", + "/api/ingest/medication", +]); + +describe("the gate's 403 in the published contract", () => { + it("is declared, naming its code, on every operation behind the gate", () => { + const behind = operations().filter( + ({ path, op }) => + op.responses?.["401"] && + !(Array.isArray(op.security) && op.security.length === 0) && + !URL_CREDENTIAL_OR_OWN_GATE.has(path), + ); + expect(behind.length).toBeGreaterThan(500); + + const silent = behind.filter(({ path, op }) => { + const code = path.startsWith("/api/admin/") + ? "auth.admin.required" + : "auth.scope.insufficient"; + return !op.responses?.["403"]?.description?.includes(code); + }); + expect(silent.map(({ path, method }) => `${method} ${path}`)).toEqual([]); + }); + + it("is not declared on an operation that takes no credential", () => { + const open = operations().filter( + ({ op }) => Array.isArray(op.security) && op.security.length === 0, + ); + expect(open.length).toBeGreaterThan(0); + const claimed = open.filter(({ op }) => + op.responses?.["403"]?.description?.includes("auth.scope.insufficient"), + ); + expect(claimed.map(({ path, method }) => `${method} ${path}`)).toEqual([]); + }); + + it("keeps an operation's own 403 and its wording", () => { + const calendar = ( + openApiPaths["/api/cycle/calendar"] as unknown as { + get: Op; + } + ).get; + const description = calendar.responses?.["403"]?.description ?? ""; + expect(description).toContain("cycle.disabled"); + expect(description).toContain("auth.scope.insufficient"); + }); +}); + +describe("withScopeRefusal", () => { + const ownForbidden = { + description: "Module off. `meta.errorCode` = `module.disabled`.", + content: { "application/json": { schema: errorEnvelope } }, + }; + const table = withScopeRefusal({ + "/api/a": { + get: { responses: { "200": {}, ...stdResponses } }, + post: { responses: { "200": {}, ...stdResponses, "403": ownForbidden } }, + }, + "/api/b": { + get: { responses: { "200": {}, "403": ownForbidden, ...stdResponses } }, + }, + "/api/admin/x": { get: { responses: { "200": {}, ...stdResponses } } }, + "/api/open": { + get: { security: [], responses: { "200": {}, ...stdResponses } }, + }, + "/api/no-auth-declared": { get: { responses: { "200": {} } } }, + }) as unknown as Record>; + + it("adds the shared response where an operation has none", () => { + expect(table["/api/a"].get.responses?.["403"]).toBe(scopeRefusalResponse); + expect(table["/api/admin/x"].get.responses?.["403"]).toBe( + adminRefusalResponse, + ); + }); + + it("prefixes an own 403, once per shared object, and leaves its text", () => { + const a = table["/api/a"].post.responses?.["403"]; + const b = table["/api/b"].get.responses?.["403"]; + expect(a).toBe(b); + expect(a?.description?.startsWith("`meta.errorCode` = `auth.scope")).toBe( + true, + ); + expect(a?.description?.endsWith(ownForbidden.description)).toBe(true); + // The input object is not mutated. + expect(ownForbidden.description).not.toContain("auth.scope"); + }); + + it("leaves an operation that takes no credential or declares no 401 alone", () => { + expect(table["/api/open"].get.responses?.["403"]).toBeUndefined(); + expect( + table["/api/no-auth-declared"].get.responses?.["403"], + ).toBeUndefined(); + }); +}); diff --git a/src/lib/openapi/routes/index.ts b/src/lib/openapi/routes/index.ts index e72f9655a..31da5dec6 100644 --- a/src/lib/openapi/routes/index.ts +++ b/src/lib/openapi/routes/index.ts @@ -96,9 +96,9 @@ import { settingsPaths } from "./settings"; import { symptomPaths } from "./symptoms"; import { syncPaths } from "./sync"; import { workoutPaths } from "./workouts"; -import { coachPrefsSchema } from "./shared"; +import { coachPrefsSchema, withScopeRefusal } from "./shared"; -export const openApiPaths: NonNullable = { +const registeredPaths: NonNullable = { ...cyclePaths, ...metaPaths, ...healthRecordPaths, @@ -214,6 +214,11 @@ export const openApiPaths: NonNullable = { ...retiredPaths, }; +// The generic auth gate's 403 is declared on the assembled table, not spread +// from `stdResponses`; `scopeRefusalResponse` in `./shared` says why. +export const openApiPaths: NonNullable = + withScopeRefusal(registeredPaths); + export const openApiComponents: NonNullable = { // Schemas listed here are forced into `components.schemas` even when no // route references them directly. `Medication` lives in this slot diff --git a/src/lib/openapi/routes/shared.ts b/src/lib/openapi/routes/shared.ts index 1e929b18e..8dfe8b32b 100644 --- a/src/lib/openapi/routes/shared.ts +++ b/src/lib/openapi/routes/shared.ts @@ -351,6 +351,128 @@ export const stdResponses = { }, }; +// ── The generic 403 ────────────────────────────────────────────────── + +/** + * The 403 the generic auth gates answer before a route's own body runs. + * + * `requireAuth` refuses a narrow Bearer token on a route that names no scope + * the token holds (`auth.scope.insufficient`), and `requireAdmin` refuses a + * session that is not an administrator's (`auth.admin.required`). The shared + * 401 described both in its prose and no operation declared the status, so a + * decoder generated from the contract had no typed branch for a refusal any + * authenticated route can send. + * + * Neither is a member of `stdResponses`, on purpose. That set is spread into + * several hundred operations, about three hundred of them declare a 403 of + * their own, and most spread the set AFTER their own entries — a shared 403 + * in it would silently overwrite every module-off and sharing refusal in the + * document. `withScopeRefusal` adds them to the assembled table instead, where + * the order of a spread cannot decide it. + */ +export const scopeRefusalResponse = { + description: + "The credential is valid but its scope does not reach this operation. `meta.errorCode` = `auth.scope.insufficient`: a narrow Bearer token on a route that names no scope it holds, or a connector-bound token on a write. Retrying with the same token will not help; use a session, or a token minted with the scope the route names.", + content: { "application/json": { schema: errorEnvelope } }, +}; + +/** + * The admin surfaces' variant. `requireAdmin` reads only the session cookie, + * so a Bearer caller never gets this far (it is a 401 there); the 403 is a + * signed-in account that is not an administrator. + */ +export const adminRefusalResponse = { + description: + "The session is valid but its account is not an administrator. `meta.errorCode` = `auth.admin.required`.", + content: { "application/json": { schema: errorEnvelope } }, +}; + +const GATE_SENTENCE = { + scope: + "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: ", + admin: + "`meta.errorCode` = `auth.admin.required` when the session's account is not an administrator; the admin gate answers it before the route's own checks run. Otherwise: ", +} as const; + +const GATE_CODE = { + scope: "auth.scope.insufficient", + admin: "auth.admin.required", +} as const; + +/** + * Surfaces that authenticate by a credential of their own and never pass the + * generic gate, so they cannot answer its 403: a URL credential (the + * clinician share link, the notification quick answer) or the external ingest + * token, which runs its own scope check and documents its own 403. + */ +const OUTSIDE_THE_GENERIC_GATE = new Set([ + "/api/c/{token}/unlock", + "/api/notifications/action/{token}", + "/api/ingest/medication", +]); + +/** + * Declare the gate's 403 on every operation that passes the generic auth + * gate: one that declares a 401 and does not opt out of authentication with + * `security: []`. Under `/api/admin/` the gate is `requireAdmin`, everywhere + * else `requireAuth`. An operation without a 403 of its own gets the shared + * response; one with its own keeps it, and its description is prefixed with + * the gate's code, because a client branching on that 403 has to tell a + * module-off refusal from a gate refusal. Prefixed, not appended: the sharing + * refusal has to stay the description's closing sentence, byte for byte + * (`openapi-sharing-denial.test.ts`). + * + * Response objects shared between operations stay shared (one augmented copy + * per original), so the emitted document keeps its anchors. + */ +export function withScopeRefusal>( + paths: T, +): T { + const augmented = { + scope: new WeakMap(), + admin: new WeakMap(), + }; + const out: Record = {}; + for (const [path, item] of Object.entries(paths)) { + if (!isPlainRecord(item) || OUTSIDE_THE_GENERIC_GATE.has(path)) { + out[path] = item; + continue; + } + const gate = path.startsWith("/api/admin/") ? "admin" : "scope"; + const nextItem: Record = { ...item }; + for (const method of ["get", "put", "post", "patch", "delete"] as const) { + const op = item[method]; + if (!isPlainRecord(op) || !isPlainRecord(op.responses)) continue; + if (Array.isArray(op.security) && op.security.length === 0) continue; + if (!op.responses["401"]) continue; + const own = op.responses["403"]; + let forbidden: unknown = + gate === "admin" ? adminRefusalResponse : scopeRefusalResponse; + if (isPlainRecord(own)) { + forbidden = augmented[gate].get(own); + if (!forbidden) { + const description = + typeof own.description === "string" ? own.description : ""; + forbidden = description.includes(GATE_CODE[gate]) + ? own + : { ...own, description: GATE_SENTENCE[gate] + description }; + augmented[gate].set(own, forbidden as object); + } + } + nextItem[method] = { + ...op, + responses: { ...op.responses, "403": forbidden }, + }; + } + out[path] = nextItem; + } + return out as T; +} + +function isPlainRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + /** * The 429 the shared single-record write bucket answers with. * diff --git a/tests/integration/routes/kit-contract.test.ts b/tests/integration/routes/kit-contract.test.ts index 99e0106e1..7359258c1 100644 --- a/tests/integration/routes/kit-contract.test.ts +++ b/tests/integration/routes/kit-contract.test.ts @@ -76,15 +76,17 @@ describe("callRoute's contract check", () => { ).rejects.toThrow(/answered 409, which the contract does not declare/); }); - it("refuses a 403 that is not the generic scope refusal", async () => { + it("refuses the scope gate's 403 on an operation that declares none", async () => { + // `/api/version` takes no credential, so no 403 is declared on it; the + // generic refusal is accepted only where the contract says so. await expect( callRoute( answering(403, { data: null, - error: "nope", - meta: { errorCode: "http.forbidden" }, + error: "Insufficient scope", + meta: { errorCode: "auth.scope.insufficient" }, }), - { method: "GET", route: "/api/tokens", auth: anonymous }, + { method: "GET", route: "/api/version", auth: anonymous }, ), ).rejects.toThrow(/answered 403/); }); diff --git a/tests/kit/call-route.ts b/tests/kit/call-route.ts index 1634c3e87..d4ac7f0b6 100644 --- a/tests/kit/call-route.ts +++ b/tests/kit/call-route.ts @@ -207,20 +207,10 @@ interface ResponseObject { $ref?: string; } -/** - * The generic scope gate answers 403 `auth.scope.insufficient` on any route a - * narrow Bearer token does not reach. The registry documents that refusal in - * the prose of the shared 401 (`stdResponses`) rather than as a 403 entry on - * every operation, so an operation that declares the shared 401 is taken to - * declare this 403 as well, and nothing else is. - */ -const SCOPE_REFUSAL_CODE = "auth.scope.insufficient"; - function lookupResponse( method: HttpMethod, route: string, status: number, - body: unknown, ): ResponseObject { const pathItem = (openApiPaths as Record)[route]; if (!isRecord(pathItem)) { @@ -235,17 +225,7 @@ function lookupResponse( ); } const responses = isRecord(operation.responses) ? operation.responses : {}; - let declared = responses[String(status)] ?? responses.default; - if ( - !isRecord(declared) && - status === 403 && - isRecord(responses["401"]) && - isRecord(body) && - isRecord(body.meta) && - body.meta.errorCode === SCOPE_REFUSAL_CODE - ) { - declared = responses["401"]; - } + const declared = responses[String(status)] ?? responses.default; if (!isRecord(declared)) { throw new ContractViolation( `${method} ${route} answered ${status}, which the contract does not declare (declared: ${Object.keys(responses).join(", ")}).`, @@ -274,7 +254,7 @@ function checkContract( body: unknown, isJson: boolean, ): void { - const declared = lookupResponse(method, route, status, body); + const declared = lookupResponse(method, route, status); const content = declared.content ?? {}; const mediaTypes = Object.keys(content); if (!isJson) { From 5ca39945236f47cb8aee4defbf00fb427e7701a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:07:34 +0200 Subject: [PATCH 06/14] fix(api): declare the FHIR refusals as the OperationOutcomes they are A /api/fhir/* handler answers its own refusals as FHIR: a bad search parameter is a 400 OperationOutcome, the hourly fhir bucket a 429 OperationOutcome (issue code throttled), both served as application/fhir+json, the same way the 404 on a read already was. The contract declared no 400 and the generic JSON 429 for the 429, and no 403 for the doctorReport module gate, which every data route runs. The searches now declare the 400, every data route the 403 module.disabled envelope and the FHIR 429 with the rate-limit headers apiHandler attaches to it. The module gate keeps answering the JSON envelope: it is the errorCode every client already branches on for a disabled module, and it runs before the FHIR face does. The route tests drop their skips and assert the 429's content type and Retry-After. --- docs/api/openapi.yaml | 1058 +++++++++-------- src/lib/openapi/routes/health-record.ts | 45 + .../routes/fhir-module-gate.test.ts | 13 +- tests/integration/routes/fhir.test.ts | 8 +- 4 files changed, 591 insertions(+), 533 deletions(-) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 2ffb877dd..8638f44b0 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -399,7 +399,7 @@ paths: "403": *a2 "409": *a7 "422": *a4 - "429": &a13 + "429": &a16 description: "Rate limit exceeded. This route shares one per-account bucket with every other authenticated write — `record-write:`, 300 requests per 60 seconds — keyed on the ACTING account, so a delegate burns their own allowance rather than the record owner's. Only the routes that meter themselves with a dedicated @@ -770,7 +770,7 @@ paths: application/json: schema: $ref: "#/components/schemas/CyclePrefsPatchEnvelope" - "400": &a15 + "400": &a18 description: The request body is not parseable JSON. Nothing was read and nothing was written. `meta.errorCode` names the surface that refused it (`.body.invalid_json`). This is a client-side serialisation fault, not a validation failure — a body that parses but fails the schema is the 422 beside this. @@ -983,7 +983,7 @@ paths: type: string format: binary "401": *a1 - "403": &a12 + "403": &a15 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused: this request named a record the caller may not act on (`meta.errorCode` = `sharing.access.denied`). Byte-identical for an account @@ -1309,10 +1309,38 @@ paths: schema: type: string format: binary + "400": &a10 + description: A search parameter the server cannot honour, such as an `_offset` above 9007199254740991. The body is an + `OperationOutcome` (`issue[0].code` = `invalid`, the reason in `diagnostics`) served as + `application/fhir+json`. + content: + application/fhir+json: + schema: + type: string + format: binary "401": *a1 - "403": *a9 + "403": &a11 + description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the + generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: + the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` + carries the disabled module key (e.g. \"sleep\"). Returned even for a valid Bearer token. Clients hide the + whole module surface end-to-end rather than retry." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a5 + "429": &a12 + description: "Rate limit exceeded: the per-account `fhir:` bucket, 120 requests per hour, shared by every + `/api/fhir/*` data route. The body is an `OperationOutcome` (`issue[0].code` = `throttled`) served as + `application/fhir+json`, not the JSON error envelope. The `Retry-After`, `X-RateLimit-Limit`, + `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers describe the bucket; back off on `Retry-After`." + headers: *a8 + content: + application/fhir+json: + schema: + type: string + format: binary /api/fhir/Observation: get: tags: @@ -1356,10 +1384,11 @@ paths: schema: type: string format: binary + "400": *a10 "401": *a1 - "403": *a9 + "403": *a11 "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/MedicationStatement: get: tags: @@ -1386,10 +1415,11 @@ paths: schema: type: string format: binary + "400": *a10 "401": *a1 - "403": *a9 + "403": *a11 "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/MedicationAdministration: get: tags: @@ -1416,10 +1446,11 @@ paths: schema: type: string format: binary + "400": *a10 "401": *a1 - "403": *a9 + "403": *a11 "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/Coverage: get: tags: @@ -1447,10 +1478,11 @@ paths: schema: type: string format: binary + "400": *a10 "401": *a1 - "403": *a9 + "403": *a11 "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/Patient/{id}: get: tags: @@ -1474,7 +1506,7 @@ paths: type: string format: binary "401": *a1 - "403": *a9 + "403": *a11 "404": description: OperationOutcome (application/fhir+json). content: @@ -1483,7 +1515,7 @@ paths: type: string format: binary "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/Coverage/{id}: get: tags: @@ -1507,7 +1539,7 @@ paths: type: string format: binary "401": *a1 - "403": *a9 + "403": *a11 "404": description: OperationOutcome (application/fhir+json). content: @@ -1516,7 +1548,7 @@ paths: type: string format: binary "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/Observation/{id}: get: tags: @@ -1541,7 +1573,7 @@ paths: type: string format: binary "401": *a1 - "403": *a9 + "403": *a11 "404": description: OperationOutcome (application/fhir+json). content: @@ -1550,7 +1582,7 @@ paths: type: string format: binary "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/MedicationStatement/{id}: get: tags: @@ -1574,7 +1606,7 @@ paths: type: string format: binary "401": *a1 - "403": *a9 + "403": *a11 "404": description: OperationOutcome (application/fhir+json). content: @@ -1583,7 +1615,7 @@ paths: type: string format: binary "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/MedicationAdministration/{id}: get: tags: @@ -1607,7 +1639,7 @@ paths: type: string format: binary "401": *a1 - "403": *a9 + "403": *a11 "404": description: OperationOutcome (application/fhir+json). content: @@ -1616,7 +1648,7 @@ paths: type: string format: binary "422": *a4 - "429": *a5 + "429": *a12 /api/fhir/Patient/$everything: get: tags: @@ -1650,10 +1682,11 @@ paths: schema: type: string format: binary + "400": *a10 "401": *a1 - "403": *a9 + "403": *a11 "422": *a4 - "429": *a5 + "429": *a12 /api/export: get: tags: @@ -2177,7 +2210,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": &a10 + "409": &a13 description: 'Refused before anything was checked or spent: the request stated `clientTransport.protocol: "http"` on a non-loopback host while this server issues `Secure` session cookies, so the browser would drop the session. `meta.errorCode` = `auth.session.insecure_transport`, `meta.sessionCookieSecure` = `true`. No password was @@ -2213,7 +2246,7 @@ paths: $ref: "#/components/schemas/MfaVerifyResponse" "401": *a1 "403": *a9 - "409": *a10 + "409": *a13 "422": *a4 "429": *a5 /api/auth/oidc/native/token: @@ -2520,7 +2553,7 @@ paths: application/json: schema: $ref: "#/components/schemas/TotpSetupEnvelope" - "401": &a11 + "401": &a14 description: No session or elevation; or, on the cookie path, `auth.reproof.required` (no recent sign-in and no proof in the body) or `auth.reproof.failed` (the proof did not verify). content: @@ -2673,7 +2706,7 @@ paths: application/json: schema: $ref: "#/components/schemas/MfaWebauthnRegisterOptionsEnvelope" - "401": *a11 + "401": *a14 "403": *a9 "422": *a4 "429": *a5 @@ -2827,7 +2860,7 @@ paths: $ref: "#/components/schemas/MfaWebauthnVerifyResponse" "401": *a1 "403": *a9 - "409": *a10 + "409": *a13 "422": *a4 "429": *a5 /api/auth/passkey/login-verify: @@ -3193,7 +3226,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a10 + "409": *a13 "429": *a5 /api/auth/passkeys/{id}: patch: @@ -3815,7 +3848,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a17 + "429": &a20 description: "Too many email-address changes, and the request asked for nothing else. The address change shares one per-account bucket across both profile routes — `profile-email:`, 10 requests per 3600 seconds — keyed on the ACTING account, so an account cannot collect a fresh allowance by probing a different address. @@ -4272,7 +4305,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementsSeriesBatchResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/measurements/series: @@ -4327,7 +4360,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementsSeriesResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/measurements: @@ -4548,7 +4581,7 @@ paths: schema: $ref: "#/components/schemas/ListMeasurementsResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -4604,7 +4637,7 @@ paths: schema: $ref: "#/components/schemas/CreateMeasurementResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": description: A measurement with this data already exists — the `(userId, type, source, externalId)` dedup index rejected the write. Never for a named sleep-stage body, which answers a re-post with 200 instead. @@ -4620,7 +4653,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a13 + "429": *a16 /api/measurements/{id}: get: tags: @@ -4641,7 +4674,7 @@ paths: schema: $ref: "#/components/schemas/GetMeasurementResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Measurement not found (or owned by another user). content: @@ -4689,7 +4722,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMeasurementResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Measurement not found (or owned by another user). content: @@ -4725,7 +4758,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMeasurementResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Measurement not found (or owned by another user). content: @@ -4838,7 +4871,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementsBulkDeleteResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -4867,7 +4900,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementsRestoreResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -4900,7 +4933,7 @@ paths: schema: $ref: "#/components/schemas/SleepNightResponse" "401": *a1 - "403": &a14 + "403": &a17 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: @@ -4943,7 +4976,7 @@ paths: schema: $ref: "#/components/schemas/SleepRhythmResponse" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/workouts: @@ -4992,7 +5025,7 @@ paths: schema: $ref: "#/components/schemas/ListWorkoutsResponse" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/workouts/{id}: @@ -5028,16 +5061,7 @@ paths: schema: $ref: "#/components/schemas/GetWorkoutResponse" "401": *a1 - "403": &a21 - description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the - generic auth gate answers it before the route's own checks run. Otherwise: Module disabled for this account: - the user (or operator) has the module turned off. `meta.errorCode` = `module.disabled` and `meta.module` - carries the disabled module key (e.g. \"sleep\"). Returned even for a valid Bearer token. Clients hide the - whole module surface end-to-end rather than retry." - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "403": *a11 "404": description: Workout not found (or owned by another user). content: @@ -5189,7 +5213,7 @@ paths: schema: $ref: "#/components/schemas/ActivityCalendarEnvelope" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/activity/goals: @@ -5232,7 +5256,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a13 + "429": *a16 /api/measurements/provenance: get: tags: @@ -5357,7 +5381,7 @@ paths: schema: $ref: "#/components/schemas/DayProvenanceEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/measurements/provenance/compare: @@ -5535,7 +5559,7 @@ paths: schema: $ref: "#/components/schemas/SourceComparisonEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/devices: @@ -5647,7 +5671,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeAggregateEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": description: The query failed validation — a missing or unknown `scope`, or a `days` outside 1..365. Multi-issue envelope; an audit breadcrumb is filed under the RECORD as `medications.intake.list.validation-failed` with @@ -5777,7 +5801,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a13 + "429": *a16 /api/medications/{id}/glp1: get: tags: @@ -5802,7 +5826,7 @@ paths: schema: $ref: "#/components/schemas/Glp1DetailResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -5886,7 +5910,7 @@ paths: schema: $ref: "#/components/schemas/Glp1PostResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6025,7 +6049,7 @@ paths: schema: $ref: "#/components/schemas/ReminderPhaseConfigEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6143,7 +6167,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationSideEffectsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6182,7 +6206,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationSideEffectEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6227,7 +6251,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationSideEffectEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No such entry for this medication and this record. An unknown id, another medication's entry and another record's entry are indistinguishable. @@ -6275,7 +6299,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationIntakeEventEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No live event with that id under this medication and record. An unknown id and an already-tombstoned one are indistinguishable. @@ -6323,7 +6347,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationIntakeEventEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No event with that id under this medication and record. content: @@ -6366,7 +6390,7 @@ paths: schema: $ref: "#/components/schemas/BulkDeleteMedicationIntakeEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6408,7 +6432,7 @@ paths: schema: $ref: "#/components/schemas/PurgeMedicationIntakeEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6453,7 +6477,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportAcceptedEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another account). content: @@ -6534,7 +6558,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationsResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -6566,7 +6590,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -6586,7 +6610,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationCategoriesResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -6613,7 +6637,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationCategoryResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -6846,7 +6870,7 @@ paths: schema: $ref: "#/components/schemas/MedicationListLayoutResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 put: @@ -6881,7 +6905,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a16 + "422": &a19 description: Request validation failed. When the body carried a `baseUpdatedAt` that could not be parsed as an ISO-8601 timestamp — including an explicit `null` — `meta.errorCode` = `invalid_base_updated_at` and nothing was written. Omit the key entirely for the unconditional write; do not send `null` for it. @@ -6927,7 +6951,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -6968,7 +6992,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7042,7 +7066,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationIntakeResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7060,7 +7084,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a13 + "429": *a16 get: tags: - Medications @@ -7125,7 +7149,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationIntakeEventsResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7158,7 +7182,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationInventoryResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7194,7 +7218,7 @@ paths: schema: $ref: "#/components/schemas/CreateMedicationInventoryItemResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7238,7 +7262,7 @@ paths: schema: $ref: "#/components/schemas/UpdateMedicationInventoryItemResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Inventory item not found (or owned by another user / medication). content: @@ -7273,7 +7297,7 @@ paths: schema: $ref: "#/components/schemas/DeleteMedicationInventoryItemResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Inventory item not found (or owned by another user / medication). content: @@ -7371,7 +7395,7 @@ paths: schema: $ref: "#/components/schemas/GetMedicationCadenceResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7420,7 +7444,7 @@ paths: schema: $ref: "#/components/schemas/ListMedicationComplianceResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/medications/{id}/compliance: @@ -7605,7 +7629,7 @@ paths: schema: $ref: "#/components/schemas/ListScheduleRevisionsResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7643,7 +7667,7 @@ paths: schema: $ref: "#/components/schemas/CreateScheduleRevisionResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication not found (or owned by another user). content: @@ -7687,7 +7711,7 @@ paths: schema: $ref: "#/components/schemas/UpdateScheduleRevisionResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication or revision not found (or owned by another user). content: @@ -7728,7 +7752,7 @@ paths: schema: $ref: "#/components/schemas/DeleteScheduleRevisionResponse" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Medication or revision not found (or owned by another user). content: @@ -7791,7 +7815,7 @@ paths: schema: $ref: "#/components/schemas/MedicationDoseHistoryImportQueuedEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": description: A dose-history import is already in progress. content: @@ -7853,7 +7877,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportJobStatusEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No such job for this medication (or owned by another user). content: @@ -7887,7 +7911,7 @@ paths: schema: $ref: "#/components/schemas/MedicationIntakeImportJobStatusEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No such job for this account (or owned by another user). content: @@ -7917,7 +7941,7 @@ paths: schema: $ref: "#/components/schemas/ProfileSummaryEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/ai/test: @@ -8127,7 +8151,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutCoachPrefsResponse" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "409": @@ -8138,7 +8162,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 /api/auth/me/disable-coach: get: @@ -8177,7 +8201,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchDisableCoachResponse" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "422": *a4 @@ -8338,7 +8362,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchModulesResponse" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "409": @@ -8349,7 +8373,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 /api/auth/me/source-priority: get: @@ -9301,7 +9325,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutSourcePriorityResponse" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "422": *a4 @@ -9461,7 +9485,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a17 + "429": *a20 /api/auth/me/notification-prefs: get: tags: @@ -9504,7 +9528,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchNotificationPrefsResponse" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "409": @@ -9515,7 +9539,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 /api/auth/me/report-selection: get: @@ -9558,7 +9582,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutReportSelectionResponseEnvelope" - "400": *a15 + "400": *a18 "401": *a1 "403": *a9 "422": *a4 @@ -10352,7 +10376,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a20 + "503": &a23 description: "The AI capability could not be resolved (its inputs could not be read) and the answer failed closed: `meta.errorCode` = `ai.unavailable`, `meta.reason` = `check_failed`. Retry later; no data depends on it." content: @@ -10771,7 +10795,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 delete: tags: @@ -10953,7 +10977,7 @@ paths: schema: $ref: "#/components/schemas/CoachNudgeStatus" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/coach/seen: @@ -11074,7 +11098,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 /api/anamnesis/facts: get: @@ -11093,7 +11117,7 @@ paths: schema: $ref: "#/components/schemas/GetAnamnesisFactsResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -11568,7 +11592,7 @@ paths: $ref: "#/components/schemas/CorrectAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": &a18 + "404": &a21 description: No current revision with this id exists for the authenticated account. content: application/json: @@ -11605,7 +11629,7 @@ paths: $ref: "#/components/schemas/RemoveAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": *a18 + "404": *a21 "409": description: Anamnesis fact changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -11976,7 +12000,7 @@ paths: schema: $ref: "#/components/schemas/CoachRemindersList" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -12144,7 +12168,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteList" "401": *a1 - "403": &a19 + "403": &a22 description: The session is valid but its account is not an administrator. `meta.errorCode` = `auth.admin.required`. content: application/json: @@ -12185,7 +12209,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteCreated" "401": *a1 - "403": *a19 + "403": *a22 "422": *a4 "429": *a5 /api/admin/invites/{id}: @@ -12210,7 +12234,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteRevoked" "401": *a1 - "403": *a19 + "403": *a22 "422": *a4 "429": *a5 /api/insights/chat/messages/{id}/feedback: @@ -12266,7 +12290,7 @@ paths: schema: $ref: "#/components/schemas/AdminMeasurementMaintenanceStatusResponse" "401": *a1 - "403": *a19 + "403": *a22 "422": *a4 "429": *a5 post: @@ -12396,7 +12420,7 @@ paths: schema: $ref: "#/components/schemas/DashboardSnapshotResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/comprehensive: @@ -12418,7 +12442,7 @@ paths: schema: $ref: "#/components/schemas/InsightsComprehensiveResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/cards: @@ -12439,7 +12463,7 @@ paths: schema: $ref: "#/components/schemas/InsightsCardsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/pregenerate: @@ -12577,7 +12601,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a20 + "503": *a23 post: tags: - Insights @@ -12783,7 +12807,7 @@ paths: schema: $ref: "#/components/schemas/CoachReadStripEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/narrative: @@ -12832,7 +12856,7 @@ paths: schema: $ref: "#/components/schemas/NarrativeEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/glp1-timeline: @@ -12869,7 +12893,7 @@ paths: schema: $ref: "#/components/schemas/Glp1TimelineEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/pulse/intraday: @@ -12905,7 +12929,7 @@ paths: schema: $ref: "#/components/schemas/IntradayPulseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/settings: @@ -12981,7 +13005,7 @@ paths: schema: $ref: "#/components/schemas/TargetsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/analytics/range: @@ -13055,7 +13079,7 @@ paths: schema: $ref: "#/components/schemas/BloodPressureStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/pulse-status: @@ -13087,7 +13111,7 @@ paths: schema: $ref: "#/components/schemas/PulseStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/weight-status: @@ -13119,7 +13143,7 @@ paths: schema: $ref: "#/components/schemas/WeightStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/bmi-status: @@ -13151,7 +13175,7 @@ paths: schema: $ref: "#/components/schemas/BmiStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/mood-status: @@ -13183,7 +13207,7 @@ paths: schema: $ref: "#/components/schemas/MoodStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/medication-compliance-status: @@ -13216,7 +13240,7 @@ paths: schema: $ref: "#/components/schemas/MedicationComplianceStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/metric-status: @@ -13311,7 +13335,7 @@ paths: schema: $ref: "#/components/schemas/MetricStatusResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/biomarker-assessment: @@ -13356,7 +13380,7 @@ paths: schema: $ref: "#/components/schemas/BiomarkerAssessmentResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/derived: @@ -13468,7 +13492,7 @@ paths: schema: $ref: "#/components/schemas/DerivedMetricResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/derived/batch: @@ -13508,7 +13532,7 @@ paths: schema: $ref: "#/components/schemas/DerivedBatchResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/score-history: @@ -13558,7 +13582,7 @@ paths: schema: $ref: "#/components/schemas/ScoreHistoryResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/glp1-plateau: @@ -13578,7 +13602,7 @@ paths: schema: $ref: "#/components/schemas/InsightsGlp1PlateauResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/ecg: @@ -13600,7 +13624,7 @@ paths: schema: $ref: "#/components/schemas/EcgListResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -13678,7 +13702,7 @@ paths: schema: $ref: "#/components/schemas/EcgDetailResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No ECG recording with that id for the authenticated user (existence sealed — a foreign id is indistinguishable from a missing one). @@ -13710,7 +13734,7 @@ paths: schema: $ref: "#/components/schemas/RhythmEventsResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/correlations: @@ -13732,7 +13756,7 @@ paths: schema: $ref: "#/components/schemas/CorrelationDiscoveryResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/patterns: @@ -13750,7 +13774,7 @@ paths: schema: $ref: "#/components/schemas/CorrelationPatternListResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/patterns/{id}: @@ -13780,7 +13804,7 @@ paths: schema: $ref: "#/components/schemas/UpdateCorrelationPatternResponseEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: No current pattern with that id for this account. content: @@ -13805,7 +13829,7 @@ paths: schema: $ref: "#/components/schemas/InsightsHealthStatusEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/breathing-screening: @@ -13824,7 +13848,7 @@ paths: schema: $ref: "#/components/schemas/InsightsBreathingScreeningEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/insights/labs-changes: @@ -13843,7 +13867,7 @@ paths: schema: $ref: "#/components/schemas/InsightsLabsChangesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/mood/analytics: @@ -13866,7 +13890,7 @@ paths: schema: $ref: "#/components/schemas/MoodDailySeriesEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/mood/insights: @@ -13901,7 +13925,7 @@ paths: schema: $ref: "#/components/schemas/MoodAggregatesEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/mood/prognosis: @@ -13924,7 +13948,7 @@ paths: schema: $ref: "#/components/schemas/MoodPrognosisEnvelope" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/mood-entries/bulk: @@ -14038,7 +14062,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagPatchEnvelope" "401": *a1 "403": *a9 - "404": &a22 + "404": &a24 description: Not found / not owned by the caller. content: application/json: @@ -14074,7 +14098,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a22 + "404": *a24 "422": *a4 "429": *a5 /api/mood/tags/{key}/hidden: @@ -14111,7 +14135,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "404": *a22 + "404": *a24 "422": *a4 "429": *a5 /api/mood/tags/groups: @@ -14166,7 +14190,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupPatchEnvelope" "401": *a1 "403": *a9 - "404": *a22 + "404": *a24 "422": *a4 "429": *a5 delete: @@ -14198,7 +14222,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a22 + "404": *a24 "422": *a4 "429": *a5 /api/mood/tags/layout: @@ -14248,7 +14272,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 /api/mood-entries: get: @@ -14332,7 +14356,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntryListEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -14365,8 +14389,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a12 - "409": &a23 + "403": *a15 + "409": &a25 description: A mood entry with the same `(date, moodLoggedAt)` instant already exists. `meta.errorCode` = `mood.duplicate_timestamp`. Only a request without `externalId` can answer this — with one, the write upserts in place. @@ -14375,7 +14399,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a13 + "429": *a16 /api/mood-entries/{id}: get: tags: @@ -14397,8 +14421,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a12 - "404": *a22 + "403": *a15 + "404": *a24 "422": *a4 "429": *a5 put: @@ -14432,9 +14456,9 @@ paths: schema: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 - "403": *a12 - "404": *a22 - "409": *a23 + "403": *a15 + "404": *a24 + "409": *a25 "422": *a4 "429": *a5 delete: @@ -14459,8 +14483,8 @@ paths: schema: $ref: "#/components/schemas/MoodEntryDeleteEnvelope" "401": *a1 - "403": *a12 - "404": *a22 + "403": *a15 + "404": *a24 "422": *a4 "429": *a5 /api/mood-entries/restore: @@ -14489,7 +14513,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntriesRestoreEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -14518,7 +14542,7 @@ paths: schema: $ref: "#/components/schemas/MoodEntriesBulkDeleteEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -14562,7 +14586,7 @@ paths: schema: $ref: "#/components/schemas/MoodLinkedContextEnvelope" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/settings/ntfy: @@ -14611,7 +14635,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutNtfySettingsResponse" - "400": &a24 + "400": &a26 description: Body is not parseable JSON. content: application/json: @@ -14626,13 +14650,13 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": &a25 + "413": &a27 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a26 + "415": &a28 description: Content-Type is not `application/json`. content: application/json: @@ -14693,7 +14717,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutTelegramSettingsResponse" - "400": *a24 + "400": *a26 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -14709,8 +14733,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": *a25 - "415": *a26 + "413": *a27 + "415": *a28 "422": description: Body matched neither accepted shape; or `enabled` is true without both a bot token and a chat id; or the deployment's `APP_URL` is not a public HTTPS host on port 80/88/443/8443; or Telegram refused the webhook @@ -14765,11 +14789,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PutWebhookSettingsResponse" - "400": *a24 + "400": *a26 "401": *a1 "403": *a9 - "413": *a25 - "415": *a26 + "413": *a27 + "415": *a28 "422": description: Body matched neither accepted shape, or `enabled` is true while no webhook URL is stored; or the URL is on a private network the operator has not listed in `NOTIFICATION_PRIVATE_ORIGINS` (`meta.errorCode` = @@ -14796,7 +14820,7 @@ paths: schema: $ref: "#/components/schemas/GetReminderThresholdsResponse" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/settings/global-services: @@ -15434,7 +15458,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentLatestForKindEnvelope" - $ref: "#/components/schemas/ConsentLatestByKindEnvelope" - "400": &a27 + "400": &a29 description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`, `ai_extraction`. Multi-issue envelope; nothing was read or revoked. The consent family answers 400 here where the rest of the API answers 422." @@ -15495,7 +15519,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentRevokeForKindEnvelope" - $ref: "#/components/schemas/ConsentRevokeAllEnvelope" - "400": *a27 + "400": *a29 "401": *a1 "403": *a9 "422": *a4 @@ -16238,7 +16262,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderListEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -16263,7 +16287,7 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderCreatedEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -16287,8 +16311,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderEnvelope" "401": *a1 - "403": *a12 - "404": &a28 + "403": *a15 + "404": &a30 description: Measurement reminder not found / not owned. content: application/json: @@ -16325,8 +16349,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderPatchEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 delete: @@ -16348,8 +16372,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderDeleteEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/satisfy: @@ -16373,8 +16397,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSatisfyEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/complete: @@ -16401,8 +16425,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderCompleteEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/skip: @@ -16431,8 +16455,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSkipEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/snooze: @@ -16465,8 +16489,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderSnoozeEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/history: @@ -16506,8 +16530,8 @@ paths: schema: $ref: "#/components/schemas/MeasurementReminderHistoryEnvelope" "401": *a1 - "403": *a12 - "404": *a28 + "403": *a15 + "404": *a30 "422": *a4 "429": *a5 /api/labs: @@ -16585,7 +16609,7 @@ paths: schema: $ref: "#/components/schemas/ListLabResultsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -16612,7 +16636,7 @@ paths: schema: $ref: "#/components/schemas/CreateLabResultResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": description: "Nothing was written. `meta.errorCode` = `labs.create.invalid` when the body failed validation (every issue @@ -16627,7 +16651,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a13 + "429": *a16 /api/labs/overview: get: tags: @@ -16645,7 +16669,7 @@ paths: schema: $ref: "#/components/schemas/LabOverviewEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/labs/{id}: @@ -16668,8 +16692,8 @@ paths: schema: $ref: "#/components/schemas/GetLabResultResponse" "401": *a1 - "403": *a12 - "404": &a29 + "403": *a15 + "404": &a31 description: Lab result not found (or owned by another user). content: application/json: @@ -16703,8 +16727,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLabResultResponse" "401": *a1 - "403": *a12 - "404": *a29 + "403": *a15 + "404": *a31 "422": *a4 "429": *a5 delete: @@ -16726,8 +16750,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLabResultResponse" "401": *a1 - "403": *a12 - "404": *a29 + "403": *a15 + "404": *a31 "422": *a4 "429": *a5 /api/labs/restore: @@ -16753,7 +16777,7 @@ paths: schema: $ref: "#/components/schemas/RestoreLabResultsResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -16815,7 +16839,7 @@ paths: schema: $ref: "#/components/schemas/OcrTextExtractRequest" responses: - "202": &a39 + "202": &a41 description: Queued for the background worker. Every refusal the route can answer quickly (capability, provider, rate limit, budget, validation) has already been answered; anything that goes wrong from here is reported on the run. @@ -16939,7 +16963,7 @@ paths: schema: $ref: "#/components/schemas/ListBiomarkersEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -16964,7 +16988,7 @@ paths: schema: $ref: "#/components/schemas/CreateBiomarkerResponse" "401": *a1 - "403": *a12 + "403": *a15 "409": description: A biomarker with this name already exists for the caller. content: @@ -16972,7 +16996,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a13 + "429": *a16 /api/biomarkers/{id}: get: tags: @@ -16995,8 +17019,8 @@ paths: schema: $ref: "#/components/schemas/GetBiomarkerResponse" "401": *a1 - "403": *a12 - "404": &a30 + "403": *a15 + "404": &a32 description: "Biomarker not found (or owned by another user). `meta.errorCode` = `labs.biomarker.notFound`. Since v1.43 a marker that was merged into another answers with `meta.mergedInto` naming the marker it went into: drop the cached marker and open that one." @@ -17037,8 +17061,8 @@ paths: schema: $ref: "#/components/schemas/UpdateBiomarkerResponse" "401": *a1 - "403": *a12 - "404": *a30 + "403": *a15 + "404": *a32 "409": description: "`labs.biomarker.duplicate`: a rename collides with another of the caller's markers. `labs.biomarker.alias.taken` (v1.43): one of `aliases` is already the name or an alias of another marker; @@ -17071,8 +17095,8 @@ paths: schema: $ref: "#/components/schemas/DeleteBiomarkerResponse" "401": *a1 - "403": *a12 - "404": *a30 + "403": *a15 + "404": *a32 "422": *a4 "429": *a5 /api/biomarkers/{id}/merge: @@ -17109,8 +17133,8 @@ paths: schema: $ref: "#/components/schemas/MergeBiomarkersResponse" "401": *a1 - "403": *a12 - "404": *a30 + "403": *a15 + "404": *a32 "409": *a7 "422": description: "`labs.biomarker.merge.invalid`: a malformed body, or the target among the sources (Zod issues in @@ -17121,7 +17145,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": *a13 + "429": *a16 /api/encounters: get: tags: @@ -17184,7 +17208,7 @@ paths: schema: $ref: "#/components/schemas/ListEncountersEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -17210,10 +17234,10 @@ paths: schema: $ref: "#/components/schemas/CreateEncounterEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 - "429": *a13 + "429": *a16 /api/encounters/procedures: get: tags: @@ -17244,7 +17268,7 @@ paths: schema: $ref: "#/components/schemas/ListProceduresEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/body-sites: @@ -17281,7 +17305,7 @@ paths: schema: $ref: "#/components/schemas/ListBodySitesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/encounters/suggest: @@ -17311,7 +17335,7 @@ paths: schema: $ref: "#/components/schemas/SuggestEncounterEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/encounters/{id}: @@ -17334,8 +17358,8 @@ paths: schema: $ref: "#/components/schemas/GetEncounterEnvelope" "401": *a1 - "403": *a12 - "404": &a31 + "403": *a15 + "404": &a33 description: Visit not found (or owned by another user). content: application/json: @@ -17368,8 +17392,8 @@ paths: schema: $ref: "#/components/schemas/UpdateEncounterEnvelope" "401": *a1 - "403": *a12 - "404": *a31 + "403": *a15 + "404": *a33 "422": *a4 "429": *a5 delete: @@ -17393,8 +17417,8 @@ paths: schema: $ref: "#/components/schemas/DeleteEncounterEnvelope" "401": *a1 - "403": *a12 - "404": *a31 + "403": *a15 + "404": *a33 "422": *a4 "429": *a5 /api/encounters/{id}/restore: @@ -17419,8 +17443,8 @@ paths: schema: $ref: "#/components/schemas/RestoreEncounterEnvelope" "401": *a1 - "403": *a12 - "404": *a31 + "403": *a15 + "404": *a33 "422": *a4 "429": *a5 /api/encounters/{id}/links: @@ -17452,7 +17476,7 @@ paths: $ref: "#/components/schemas/LinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a31 + "404": *a33 "422": *a4 "429": *a5 delete: @@ -17482,7 +17506,7 @@ paths: $ref: "#/components/schemas/UnlinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a31 + "404": *a33 "422": *a4 "429": *a5 /api/practitioners: @@ -17522,7 +17546,7 @@ paths: schema: $ref: "#/components/schemas/ListPractitionersEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -17546,7 +17570,7 @@ paths: schema: $ref: "#/components/schemas/CreatePractitionerEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -17570,8 +17594,8 @@ paths: schema: $ref: "#/components/schemas/GetPractitionerEnvelope" "401": *a1 - "403": *a12 - "404": &a32 + "403": *a15 + "404": &a34 description: Practitioner not found (or owned by another user). content: application/json: @@ -17604,8 +17628,8 @@ paths: schema: $ref: "#/components/schemas/UpdatePractitionerEnvelope" "401": *a1 - "403": *a12 - "404": *a32 + "403": *a15 + "404": *a34 "422": *a4 "429": *a5 delete: @@ -17629,8 +17653,8 @@ paths: schema: $ref: "#/components/schemas/DeletePractitionerEnvelope" "401": *a1 - "403": *a12 - "404": *a32 + "403": *a15 + "404": *a34 "422": *a4 "429": *a5 /api/practitioners/{id}/restore: @@ -17655,8 +17679,8 @@ paths: schema: $ref: "#/components/schemas/RestorePractitionerEnvelope" "401": *a1 - "403": *a12 - "404": *a32 + "403": *a15 + "404": *a34 "422": *a4 "429": *a5 /api/vaccinations/custom: @@ -17674,7 +17698,7 @@ paths: schema: $ref: "#/components/schemas/ListCustomVaccinesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -17699,8 +17723,8 @@ paths: schema: $ref: "#/components/schemas/CreateCustomVaccineEnvelope" "401": *a1 - "403": *a12 - "409": &a33 + "403": *a15 + "409": &a35 description: A live definition on the record already carries this name, compared without case (`vaccination.custom.name-taken`). content: @@ -17708,7 +17732,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 - "429": *a13 + "429": *a16 /api/vaccinations/custom/{id}: patch: tags: @@ -17736,14 +17760,14 @@ paths: schema: $ref: "#/components/schemas/UpdateCustomVaccineEnvelope" "401": *a1 - "403": *a12 - "404": &a34 + "403": *a15 + "404": &a36 description: No live definition with this id on the record (`vaccination.custom.not-found`). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a33 + "409": *a35 "422": *a4 "429": *a5 delete: @@ -17767,8 +17791,8 @@ paths: schema: $ref: "#/components/schemas/DeleteCustomVaccineEnvelope" "401": *a1 - "403": *a12 - "404": *a34 + "403": *a15 + "404": *a36 "422": *a4 "429": *a5 /api/vaccinations: @@ -17821,7 +17845,7 @@ paths: schema: $ref: "#/components/schemas/ListVaccinationsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -17848,7 +17872,7 @@ paths: schema: $ref: "#/components/schemas/CreateVaccinationEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: A practice, visit or own vaccine the body names is not the record's (`vaccination.practitioner-not-found`, `vaccination.encounter-not-found`, `vaccination.custom-vaccine-not-found`). @@ -17858,7 +17882,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "409": *a7 "422": *a4 - "429": *a13 + "429": *a16 /api/vaccinations/{id}: get: tags: @@ -17879,8 +17903,8 @@ paths: schema: $ref: "#/components/schemas/GetVaccinationEnvelope" "401": *a1 - "403": *a12 - "404": &a35 + "403": *a15 + "404": &a37 description: Vaccination not found (or owned by another user). content: application/json: @@ -17914,8 +17938,8 @@ paths: schema: $ref: "#/components/schemas/UpdateVaccinationEnvelope" "401": *a1 - "403": *a12 - "404": *a35 + "403": *a15 + "404": *a37 "422": *a4 "429": *a5 delete: @@ -17939,8 +17963,8 @@ paths: schema: $ref: "#/components/schemas/DeleteVaccinationEnvelope" "401": *a1 - "403": *a12 - "404": *a35 + "403": *a15 + "404": *a37 "422": *a4 "429": *a5 /api/vaccinations/{id}/restore: @@ -17964,8 +17988,8 @@ paths: schema: $ref: "#/components/schemas/RestoreVaccinationEnvelope" "401": *a1 - "403": *a12 - "404": *a35 + "403": *a15 + "404": *a37 "422": *a4 "429": *a5 /api/vaccinations/{id}/booster: @@ -18033,7 +18057,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": *a35 + "404": *a37 "422": *a4 "429": *a5 /api/vaccinations/suggest: @@ -18061,7 +18085,7 @@ paths: schema: $ref: "#/components/schemas/VaccinationSuggestEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/vaccinations/{id}/links: @@ -18094,7 +18118,7 @@ paths: $ref: "#/components/schemas/LinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a35 + "404": *a37 "422": *a4 "429": *a5 delete: @@ -18124,7 +18148,7 @@ paths: $ref: "#/components/schemas/UnlinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a35 + "404": *a37 "422": *a4 "429": *a5 /api/illness/episodes: @@ -18188,7 +18212,7 @@ paths: schema: $ref: "#/components/schemas/ListIllnessEpisodesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -18214,7 +18238,7 @@ paths: schema: $ref: "#/components/schemas/CreateIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -18238,8 +18262,8 @@ paths: schema: $ref: "#/components/schemas/GetIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 - "404": &a36 + "403": *a15 + "404": &a38 description: Illness episode not found (or owned by another user). content: application/json: @@ -18273,8 +18297,8 @@ paths: schema: $ref: "#/components/schemas/UpdateIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "422": *a4 "429": *a5 delete: @@ -18297,8 +18321,8 @@ paths: schema: $ref: "#/components/schemas/DeleteIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "422": *a4 "429": *a5 /api/illness/episodes/{id}/restore: @@ -18325,8 +18349,8 @@ paths: schema: $ref: "#/components/schemas/RestoreIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "409": *a7 "422": *a4 "429": *a5 @@ -18358,8 +18382,8 @@ paths: schema: $ref: "#/components/schemas/ResolveIllnessEpisodeEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "422": *a4 "429": *a5 /api/illness/episodes/{id}/day-logs: @@ -18409,8 +18433,8 @@ paths: schema: $ref: "#/components/schemas/GetIllnessDayLogEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "422": *a4 "429": *a5 post: @@ -18446,8 +18470,8 @@ paths: schema: $ref: "#/components/schemas/UpsertIllnessDayLogCreatedEnvelope" "401": *a1 - "403": *a12 - "404": *a36 + "403": *a15 + "404": *a38 "422": *a4 "429": *a5 /api/illness/episodes/{id}/correlation: @@ -18475,7 +18499,7 @@ paths: $ref: "#/components/schemas/IllnessCorrelationEnvelope" "401": *a1 "403": *a9 - "404": *a36 + "404": *a38 "422": *a4 "429": *a5 /api/illness/insights: @@ -18549,7 +18573,7 @@ paths: schema: $ref: "#/components/schemas/ListMentalHealthAssessmentsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -18582,7 +18606,7 @@ paths: schema: $ref: "#/components/schemas/CreateMentalHealthAssessmentEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -18609,7 +18633,7 @@ paths: schema: $ref: "#/components/schemas/GetMentalHealthAssessmentEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/allergies: @@ -18642,7 +18666,7 @@ paths: schema: $ref: "#/components/schemas/ListAllergiesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -18667,10 +18691,10 @@ paths: schema: $ref: "#/components/schemas/CreateAllergyEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 - "429": *a13 + "429": *a16 /api/allergies/{id}: get: tags: @@ -18691,8 +18715,8 @@ paths: schema: $ref: "#/components/schemas/GetAllergyEnvelope" "401": *a1 - "403": *a12 - "404": &a37 + "403": *a15 + "404": &a39 description: Allergy not found (or owned by another user). content: application/json: @@ -18725,8 +18749,8 @@ paths: schema: $ref: "#/components/schemas/UpdateAllergyEnvelope" "401": *a1 - "403": *a12 - "404": *a37 + "403": *a15 + "404": *a39 "422": *a4 "429": *a5 delete: @@ -18749,8 +18773,8 @@ paths: schema: $ref: "#/components/schemas/DeleteAllergyEnvelope" "401": *a1 - "403": *a12 - "404": *a37 + "403": *a15 + "404": *a39 "422": *a4 "429": *a5 /api/family-history: @@ -18774,7 +18798,7 @@ paths: schema: $ref: "#/components/schemas/ListFamilyHistoryEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -18799,7 +18823,7 @@ paths: schema: $ref: "#/components/schemas/CreateFamilyHistoryEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -18823,8 +18847,8 @@ paths: schema: $ref: "#/components/schemas/GetFamilyHistoryEnvelope" "401": *a1 - "403": *a12 - "404": &a38 + "403": *a15 + "404": &a40 description: Family history entry not found (or owned by another user). content: application/json: @@ -18857,8 +18881,8 @@ paths: schema: $ref: "#/components/schemas/UpdateFamilyHistoryEnvelope" "401": *a1 - "403": *a12 - "404": *a38 + "403": *a15 + "404": *a40 "422": *a4 "429": *a5 delete: @@ -18881,8 +18905,8 @@ paths: schema: $ref: "#/components/schemas/DeleteFamilyHistoryEnvelope" "401": *a1 - "403": *a12 - "404": *a38 + "403": *a15 + "404": *a40 "422": *a4 "429": *a5 /api/documents/inbound: @@ -18997,7 +19021,7 @@ paths: schema: $ref: "#/components/schemas/InboundDocumentListEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -19283,7 +19307,7 @@ paths: schema: $ref: "#/components/schemas/DocumentUsageEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/documents/inbound/layout: @@ -19301,7 +19325,7 @@ paths: schema: $ref: "#/components/schemas/DocumentsLayoutEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 put: @@ -19372,7 +19396,7 @@ paths: schema: $ref: "#/components/schemas/InboundDocumentDetailEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 patch: @@ -19494,7 +19518,7 @@ paths: type: string format: binary "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/documents/inbound/{id}/thumbnail: @@ -19523,7 +19547,7 @@ paths: type: string format: binary "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/documents/inbound/{id}/extract: @@ -19555,7 +19579,7 @@ paths: required: true schema: type: string - - &a40 + - &a42 name: Prefer in: header required: false @@ -19580,9 +19604,9 @@ paths: application/json: schema: $ref: "#/components/schemas/InboundExtractEnvelope" - "202": *a39 + "202": *a41 "401": *a1 - "403": &a41 + "403": &a43 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and @@ -19608,7 +19632,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a42 + "503": &a44 description: "`ai.unavailable` — the inputs behind the AI capability could not be read, and the answer failed closed. Nothing was sent. Retry later." content: @@ -19638,7 +19662,7 @@ paths: required: true schema: type: string - - *a40 + - *a42 requestBody: required: false content: @@ -19674,12 +19698,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSuggestEnvelope" - "202": *a39 + "202": *a41 "401": *a1 - "403": *a41 + "403": *a43 "422": *a4 "429": *a5 - "503": *a42 + "503": *a44 /api/documents/inbound/{id}/summary: post: tags: @@ -19729,7 +19753,7 @@ paths: type: boolean default: false description: With `persist=true`, explicitly replace an existing stored summary. - - *a40 + - *a42 requestBody: required: false content: @@ -19765,12 +19789,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSummaryEnvelope" - "202": *a39 + "202": *a41 "401": *a1 - "403": *a41 + "403": *a43 "422": *a4 "429": *a5 - "503": *a42 + "503": *a44 /api/documents/inbound/{id}/index: post: tags: @@ -19796,7 +19820,7 @@ paths: required: true schema: type: string - - *a40 + - *a42 requestBody: required: false content: @@ -19832,12 +19856,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentIndexEnvelope" - "202": *a39 + "202": *a41 "401": *a1 - "403": *a41 + "403": *a43 "422": *a4 "429": *a5 - "503": *a42 + "503": *a44 /api/documents/inbound/{id}/chat: post: tags: @@ -19877,10 +19901,10 @@ paths: type: string description: "SSE frames: `data: \\n\\n`. See the operation description for the per-`type` frame shapes." "401": *a1 - "403": *a41 + "403": *a43 "422": *a4 "429": *a5 - "503": *a42 + "503": *a44 get: tags: - Documents @@ -19944,10 +19968,10 @@ paths: schema: $ref: "#/components/schemas/DocumentReindexEnvelope" "401": *a1 - "403": *a41 + "403": *a43 "422": *a4 "429": *a5 - "503": *a42 + "503": *a44 /api/documents/inbound/{id}/facts/{factId}: patch: tags: @@ -20031,7 +20055,7 @@ paths: schema: $ref: "#/components/schemas/ListCustomMetricsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 post: @@ -20057,7 +20081,7 @@ paths: $ref: "#/components/schemas/CreateCustomMetricResponse" "401": *a1 "403": *a9 - "409": &a44 + "409": &a46 description: A custom metric with this name already exists for the caller. content: application/json: @@ -20085,8 +20109,8 @@ paths: schema: $ref: "#/components/schemas/GetCustomMetricResponse" "401": *a1 - "403": *a12 - "404": &a43 + "403": *a15 + "404": &a45 description: Custom metric not found (or owned by another user). content: application/json: @@ -20121,8 +20145,8 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a43 - "409": *a44 + "404": *a45 + "409": *a46 "422": *a4 "429": *a5 delete: @@ -20146,7 +20170,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a43 + "404": *a45 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries: @@ -20189,8 +20213,8 @@ paths: schema: $ref: "#/components/schemas/ListCustomMetricEntriesEnvelope" "401": *a1 - "403": *a12 - "404": *a43 + "403": *a15 + "404": *a45 "422": *a4 "429": *a5 post: @@ -20220,10 +20244,10 @@ paths: $ref: "#/components/schemas/CreateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a43 + "404": *a45 "409": *a7 "422": *a4 - "429": *a13 + "429": *a16 /api/custom-metrics/{id}/entries/{entryId}: patch: tags: @@ -20257,7 +20281,7 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": &a45 + "404": &a47 description: Custom metric entry not found (or owned by another user). content: application/json: @@ -20291,7 +20315,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a45 + "404": *a47 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries/restore: @@ -20599,7 +20623,7 @@ paths: schema: $ref: "#/components/schemas/NutrientCatalogEnvelope" "401": *a1 - "403": &a46 + "403": &a48 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is @@ -20748,7 +20772,7 @@ paths: schema: $ref: "#/components/schemas/NutrientOverviewEnvelope" "401": *a1 - "403": *a46 + "403": *a48 "422": *a4 "429": *a5 /api/nutrients/daily: @@ -20785,7 +20809,7 @@ paths: schema: $ref: "#/components/schemas/NutrientDailySeriesEnvelope" "401": *a1 - "403": *a46 + "403": *a48 "422": *a4 "429": *a5 /api/nutrients/water: @@ -20814,7 +20838,7 @@ paths: schema: $ref: "#/components/schemas/NutrientWaterWriteEnvelope" "401": *a1 - "403": *a46 + "403": *a48 "409": *a7 "422": *a4 "429": *a5 @@ -21335,7 +21359,7 @@ paths: application/json: schema: $ref: "#/components/schemas/WithingsCredentialsUpdateEnvelope" - "400": &a47 + "400": &a49 description: Body is not parseable JSON. content: application/json: @@ -21343,19 +21367,19 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "413": &a48 + "413": &a50 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a49 + "415": &a51 description: Content-Type is not `application/json`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a50 + "422": &a52 description: "`clientId` and `clientSecret` are both required and each must be 1–200 characters. The envelope carries a single fixed message, not per-field issues." content: @@ -21546,12 +21570,12 @@ paths: application/json: schema: $ref: "#/components/schemas/FitbitCredentialsUpdateEnvelope" - "400": *a47 + "400": *a49 "401": *a1 "403": *a9 - "413": *a48 - "415": *a49 - "422": *a50 + "413": *a50 + "415": *a51 + "422": *a52 "429": *a5 delete: tags: @@ -21706,12 +21730,12 @@ paths: application/json: schema: $ref: "#/components/schemas/GoogleHealthCredentialsUpdateEnvelope" - "400": *a47 + "400": *a49 "401": *a1 "403": *a9 - "413": *a48 - "415": *a49 - "422": *a50 + "413": *a50 + "415": *a51 + "422": *a52 "429": *a5 delete: tags: @@ -21877,12 +21901,12 @@ paths: application/json: schema: $ref: "#/components/schemas/OuraCredentialsUpdateEnvelope" - "400": *a47 + "400": *a49 "401": *a1 "403": *a9 - "413": *a48 - "415": *a49 - "422": *a50 + "413": *a50 + "415": *a51 + "422": *a52 "429": *a5 delete: tags: @@ -21962,21 +21986,21 @@ paths: $ref: "#/components/schemas/OuraTestEnvelope" "401": *a1 "403": *a9 - "422": &a51 + "422": &a53 description: No usable connection for this provider — nothing stored, or the stored grant could no longer be resolved into an access token. `meta.errorCode` = `not_configured`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a52 + "429": &a54 description: More than 5 requests in 60 s from this user. `meta.errorCode` = `rate_limited_self` — this is the local limiter, distinct from a `rate_limited` 502 which is the provider's. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": &a53 + "502": &a55 description: "The probe did not complete. `meta.errorCode` names the class: `credentials_rejected` (the provider answered 401/403 — the stored grant is gone), `rate_limited` (the PROVIDER answered 429, which is not this endpoint's own limiter), `upstream_error` (5xx), `timeout` (no answer within 5 s), `connection_failed` @@ -22022,12 +22046,12 @@ paths: application/json: schema: $ref: "#/components/schemas/PolarCredentialsUpdateEnvelope" - "400": *a47 + "400": *a49 "401": *a1 "403": *a9 - "413": *a48 - "415": *a49 - "422": *a50 + "413": *a50 + "415": *a51 + "422": *a52 "429": *a5 delete: tags: @@ -22107,9 +22131,9 @@ paths: $ref: "#/components/schemas/PolarTestEnvelope" "401": *a1 "403": *a9 - "422": *a51 - "429": *a52 - "502": *a53 + "422": *a53 + "429": *a54 + "502": *a55 /api/strava/credentials: get: tags: @@ -22146,12 +22170,12 @@ paths: application/json: schema: $ref: "#/components/schemas/StravaCredentialsUpdateEnvelope" - "400": *a47 + "400": *a49 "401": *a1 "403": *a9 - "413": *a48 - "415": *a49 - "422": *a50 + "413": *a50 + "415": *a51 + "422": *a52 "429": *a5 delete: tags: @@ -22233,9 +22257,9 @@ paths: $ref: "#/components/schemas/StravaTestEnvelope" "401": *a1 "403": *a9 - "422": *a51 - "429": *a52 - "502": *a53 + "422": *a53 + "429": *a54 + "502": *a55 /api/integrations/status: get: tags: @@ -22273,7 +22297,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a52 + "429": *a54 /api/integrations/fitbit/test: post: tags: @@ -22299,9 +22323,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a51 - "429": *a52 - "502": *a53 + "422": *a53 + "429": *a54 + "502": *a55 /api/integrations/google-health/resume: post: tags: @@ -22319,7 +22343,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a52 + "429": *a54 /api/integrations/google-health/test: post: tags: @@ -22353,9 +22377,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a51 - "429": *a52 - "502": *a53 + "422": *a53 + "429": *a54 + "502": *a55 /api/integrations/whoop/resume: post: tags: @@ -22373,7 +22397,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a52 + "429": *a54 /api/integrations/whoop/test: post: tags: @@ -22391,9 +22415,9 @@ paths: $ref: "#/components/schemas/WhoopIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a51 - "429": *a52 - "502": *a53 + "422": *a53 + "429": *a54 + "502": *a55 /api/integrations/withings/resume: post: tags: @@ -22412,7 +22436,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a52 + "429": *a54 /api/integrations/withings/test: post: tags: @@ -22431,8 +22455,8 @@ paths: $ref: "#/components/schemas/WithingsIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a51 - "429": *a52 + "422": *a53 + "429": *a54 "502": description: "The probe did not complete. The shared codes apply (`credentials_rejected`, `rate_limited`, `upstream_error`, `timeout`, `connection_failed`) and this path adds one of its own: @@ -22465,7 +22489,7 @@ paths: schema: $ref: "#/components/schemas/DailyDigestEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/daily/digest/dismiss: @@ -22558,7 +22582,7 @@ paths: schema: $ref: "#/components/schemas/DashboardSummaryEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/dashboard/widgets: @@ -22577,7 +22601,7 @@ paths: schema: $ref: "#/components/schemas/DashboardLayout" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 put: @@ -22612,7 +22636,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a16 + "422": *a19 "429": *a5 delete: tags: @@ -22850,7 +22874,7 @@ paths: schema: $ref: "#/components/schemas/RecordActivityEnvelope" "401": *a1 - "403": &a54 + "403": &a56 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused. `meta.errorCode` is `sharing.not_permitted` when the request was made while acting on another account (grant management is never @@ -22877,7 +22901,7 @@ paths: schema: $ref: "#/components/schemas/AccountGrantListEnvelope" "401": *a1 - "403": *a54 + "403": *a56 "422": *a4 "429": *a5 post: @@ -22981,7 +23005,7 @@ paths: schema: $ref: "#/components/schemas/AcceptedAccountGrantEnvelope" "401": *a1 - "403": *a54 + "403": *a56 "404": description: No such invitation for this account. content: @@ -23025,7 +23049,7 @@ paths: schema: $ref: "#/components/schemas/RevokedAccountGrantEnvelope" "401": *a1 - "403": *a54 + "403": *a56 "404": description: No such grant on this account's record. content: @@ -23066,7 +23090,7 @@ paths: schema: $ref: "#/components/schemas/RenouncedAccountGrantEnvelope" "401": *a1 - "403": *a54 + "403": *a56 "404": description: No such grant held by this account. content: @@ -23458,7 +23482,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a54 + "403": *a56 "409": description: "The record this browser session is on is not the record the request asserted. `meta.errorCode` is `sharing.session.changed`. It is not a refusal of access — the caller may well be entitled to both records — @@ -23847,7 +23871,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionListEnvelope" "401": *a1 - "403": &a55 + "403": &a57 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API instance-wide. Every route in this module refuses while that setting is off, for a cookie session as well as @@ -23881,7 +23905,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionRevokeEnvelope" "401": *a1 - "403": *a55 + "403": *a57 "404": description: No live connection with this id for this caller — also the answer for an already-revoked one, so a repeat revoke is 404 rather than an idempotent 200. @@ -23908,7 +23932,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenListEnvelope" "401": *a1 - "403": *a55 + "403": *a57 "422": *a4 "429": *a5 post: @@ -23941,7 +23965,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a55 + "403": *a57 "413": description: Body exceeds 16 KiB. content: @@ -23984,7 +24008,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenRevokeEnvelope" "401": *a1 - "403": *a55 + "403": *a57 "404": description: No `health:read` token with this id for this caller. An already-revoked token is still found, so a repeat revoke answers 200. @@ -24011,7 +24035,7 @@ paths: schema: $ref: "#/components/schemas/RecordSettingsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/record-settings/integrations: @@ -24030,7 +24054,7 @@ paths: schema: $ref: "#/components/schemas/RecordSettingsIntegrationsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/record-settings/integrations/{provider}/connect: @@ -24046,7 +24070,7 @@ paths: record (audited). Fitbit is not offered: its classic API is retired. Cookie session, while acting on a managed record the caller guards." parameters: - - &a56 + - &a58 name: provider in: path required: true @@ -24068,7 +24092,7 @@ paths: schema: $ref: "#/components/schemas/ManagedConnectStartEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: The path named a provider outside the closed set. Answered before the caller is authenticated. content: @@ -24093,7 +24117,7 @@ paths: Audited against the record with the Guardian as the actor. Cookie session or Bearer, while acting on a managed record the caller guards." parameters: - - *a56 + - *a58 responses: "200": description: The provider is disconnected. @@ -24102,7 +24126,7 @@ paths: schema: $ref: "#/components/schemas/ManagedDisconnectEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: The path named a provider outside the closed set, or the record has no connection to it. content: @@ -24119,7 +24143,7 @@ paths: description: Returns the named family's resolved settings for the record the session is acting on. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - &a57 + - &a59 name: family in: path required: true @@ -24143,7 +24167,7 @@ paths: schema: $ref: "#/components/schemas/GetRecordSettingsFamilyEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: Either the path named a family outside the closed set, or the record row itself is gone. The two are not distinguished, and the first is answered before the caller is authenticated at all. @@ -24163,7 +24187,7 @@ paths: ride the audit row. Body capped at 64 KiB. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - *a57 + - *a59 requestBody: required: true content: @@ -24184,7 +24208,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: The path named a family outside the closed set. Answered before authentication. content: @@ -24298,7 +24322,7 @@ paths: schema: $ref: "#/components/schemas/ListPersonalRecordsEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": description: "`metricType` or `limit` carried an unacceptable value. `meta.errorCode` = `personal_records.invalid_query`, with the offending path under `details.issues`." @@ -24351,7 +24375,7 @@ paths: schema: $ref: "#/components/schemas/AchievementsEnvelope" "401": *a1 - "403": *a14 + "403": *a17 "422": *a4 "429": *a5 /api/environment/preferences: @@ -24377,7 +24401,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentPreferencesEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/environment: @@ -24395,7 +24419,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentOverviewEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/environment/home: @@ -24432,7 +24456,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentHomeEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "413": description: Body exceeds 4 KiB. content: @@ -24492,7 +24516,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentBackfillEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "409": description: No home location is set, so there is nothing to resolve the days against (`meta.errorCode` = `environment.no_home`), or a backfill for this account is already queued in the current twenty-minute slot @@ -24552,7 +24576,7 @@ paths: schema: $ref: "#/components/schemas/EnvironmentGeocodeEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": description: "`q` was missing, empty after trimming, or longer than 120 characters. `meta.errorCode` = `environment.invalid`." @@ -24591,7 +24615,7 @@ paths: schema: $ref: "#/components/schemas/CreateEnvironmentTravelEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "413": description: Body exceeds 4 KiB. content: @@ -24644,7 +24668,7 @@ paths: schema: $ref: "#/components/schemas/DeleteEnvironmentTravelEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "404": description: No such override for this account. `meta.errorCode` = `environment.travel.not_found`. Also the answer for an id owned by somebody else — existence is not leaked. @@ -24790,13 +24814,13 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupDocument" - "403": &a58 + "403": &a60 description: Caller is not an admin. `requireAdmin()` is cookie-only, so a Bearer token lands here whatever its scope. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": &a59 + "404": &a61 description: No stored copy with this id. content: application/json: @@ -24870,8 +24894,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "403": *a58 - "404": *a59 + "403": *a60 + "404": *a61 "409": description: A restore of the same account is already queued or running (`meta.errorCode` = `backup.restore.active`, `meta.jobId` names it), the stored copy was replaced by a newer one while it was being checked @@ -24915,7 +24939,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobListResponse" - "403": *a58 + "403": *a60 /api/admin/backups/restores/{jobId}: get: tags: @@ -24938,7 +24962,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobResponse" - "403": *a58 + "403": *a60 "404": description: No restore job with this id. content: @@ -24957,7 +24981,7 @@ paths: request leaves the server. Needs neither the Documents module nor the list: with the list unset, `available` is false and any connection still stored is listed (with `originAllowed: false`) so it can be removed. Cookie session only; not delegable." - security: &a60 + security: &a62 - cookieAuth: [] responses: "200": @@ -24967,7 +24991,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourcesStatusEnvelope" "401": *a1 - "403": &a61 + "403": &a63 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused before any request to the source: `documents.sources.browserOnly` for a Bearer caller, `sharing.not_permitted` while acting on another @@ -24992,9 +25016,9 @@ paths: `documents.sources.tokenRequired`), so a stored token is never sent to an address it was not saved for. A Papra organization id is letters, digits, `_` and `-`. The token is stored encrypted (AES-256-GCM) and never returned. Ten saves and tests a minute per person. Cookie session only; not delegable." - security: *a60 + security: *a62 parameters: - - &a62 + - &a64 name: system in: path required: true @@ -25033,8 +25057,8 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceConnectionEnvelope" "401": *a1 - "403": *a61 - "404": &a63 + "403": *a63 + "404": &a65 description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. `documents.sources.notFound`: the source has no such document, or (Papra) no such organization, including @@ -25053,7 +25077,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": &a64 + "502": &a66 description: "The source could not answer usefully, with the reason in `meta.errorCode` and, when the source answered at all, its HTTP status in `meta.upstreamStatus` (its body is never passed on): `documents.sources.unreachable` (no connection, timeout, or a destination refused at dial time), `documents.sources.redirected` (the source @@ -25072,9 +25096,9 @@ paths: description: Deletes the caller's connection for this system, and its token with it. Works even after the operator removed the origin from the list, and with the Documents module switched off. `disconnected` is false when there was nothing to delete. Cookie session only; not delegable. - security: *a60 + security: *a62 parameters: - - *a62 + - *a64 responses: "200": description: Removed, or there was nothing to remove. @@ -25083,7 +25107,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceDisconnectEnvelope" "401": *a1 - "403": *a61 + "403": *a63 "422": *a4 "429": *a5 /api/documents/sources/{system}/test: @@ -25094,9 +25118,9 @@ paths: description: One request to the source with the stored token. Answers `ok` with the round-trip time and stamps `lastVerifiedAt`, or the reason in `meta.errorCode`. Shares the save's allowance of ten a minute. Cookie session only; not delegable. - security: *a60 + security: *a62 parameters: - - *a62 + - *a64 responses: "200": description: The source answered. @@ -25105,9 +25129,9 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTestEnvelope" "401": *a1 - "403": *a61 - "404": *a63 - "422": &a65 + "403": *a63 + "404": *a65 + "422": &a67 description: "`documents.sources.originNotAllowed`: the base address's origin is not on the operator's list (checked on every request, so a connection saved earlier is cut off once its origin is removed), or another validation failure." @@ -25116,7 +25140,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": *a64 + "502": *a66 /api/documents/sources/{system}/tags: get: tags: @@ -25124,9 +25148,9 @@ paths: summary: Tags of a connected archive description: The source's tags for the picker's filter, the first 250 by name, in the source's own ids. Shares the search allowance of sixty a minute. Cookie session only; not delegable. - security: *a60 + security: *a62 parameters: - - *a62 + - *a64 responses: "200": description: The tags. @@ -25135,11 +25159,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTagsEnvelope" "401": *a1 - "403": *a61 - "404": *a63 - "422": *a65 + "403": *a63 + "404": *a65 + "422": *a67 "429": *a5 - "502": *a64 + "502": *a66 /api/documents/sources/{system}/search: get: tags: @@ -25153,9 +25177,9 @@ paths: under that source key: `new`, `imported` with the vault document's `documentId`, or `deleted` (deleted in HealthLog; importing it again stores nothing). Nothing is stored. Sixty searches a minute, shared with the tag read. Cookie session only; not delegable." - security: *a60 + security: *a62 parameters: - - *a62 + - *a64 - name: q in: query required: false @@ -25196,11 +25220,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceSearchEnvelope" "401": *a1 - "403": *a61 - "404": *a63 - "422": *a65 + "403": *a63 + "404": *a65 + "422": *a67 "429": *a5 - "502": *a64 + "502": *a66 /api/documents/sources/{system}/import: post: tags: @@ -25219,9 +25243,9 @@ paths: fetched), including a duplicate. `kind` sets the document type (default OTHER). Automatic AI reading follows the person's own setting. Counts against the person's upload allowance (60 an hour) and storage quota. 201 for a new document, 200 otherwise. Cookie session only; not delegable." - security: *a60 + security: *a62 parameters: - - *a62 + - *a64 requestBody: required: true content: @@ -25279,7 +25303,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceImportEnvelope" "401": *a1 - "403": *a61 + "403": *a63 "404": description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. @@ -25310,7 +25334,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a65 + "422": *a67 "429": description: "`documents.inbound.rateLimited` (the person's hourly upload allowance, with the rate-limit headers) or `documents.inbound.uploadBusy` (too many uploads in progress; `Retry-After: 1`)." @@ -25318,7 +25342,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": *a64 + "502": *a66 /api/symptoms/definitions: get: tags: @@ -25342,7 +25366,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomDefinitionsEnvelope" "401": *a1 - "403": &a66 + "403": &a68 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `illness.disabled`: the record @@ -25386,7 +25410,7 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomDefinitionEnvelope" "401": *a1 - "403": *a66 + "403": *a68 "409": *a7 "422": *a4 "429": *a5 @@ -25416,8 +25440,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomDefinitionEnvelope" "401": *a1 - "403": *a66 - "404": &a67 + "403": *a68 + "404": &a69 description: "`symptoms.definition.notFound`: no definition with this id on the record." content: application/json: @@ -25451,8 +25475,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomDefinitionEnvelope" "401": *a1 - "403": *a66 - "404": *a67 + "403": *a68 + "404": *a69 "422": *a4 "429": *a5 /api/symptoms/events: @@ -25501,7 +25525,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomEventsEnvelope" "401": *a1 - "403": *a66 + "403": *a68 "422": *a4 "429": *a5 post: @@ -25527,8 +25551,8 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomEventEnvelope" "401": *a1 - "403": *a66 - "404": *a67 + "403": *a68 + "404": *a69 "409": *a7 "422": *a4 "429": *a5 @@ -25558,8 +25582,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomEventEnvelope" "401": *a1 - "403": *a66 - "404": &a68 + "403": *a68 + "404": &a70 description: "`symptoms.event.notFound`: no occurrence with this id on the record." content: application/json: @@ -25586,8 +25610,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomEventEnvelope" "401": *a1 - "403": *a66 - "404": *a68 + "403": *a68 + "404": *a70 "422": *a4 "429": *a5 /api/day/index: @@ -25620,7 +25644,7 @@ paths: schema: $ref: "#/components/schemas/DayIndexEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/day/notable: @@ -25655,7 +25679,7 @@ paths: schema: $ref: "#/components/schemas/DayNotableWindowEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/day/{date}: @@ -25685,7 +25709,7 @@ paths: schema: $ref: "#/components/schemas/DayEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/timeline: @@ -25738,7 +25762,7 @@ paths: schema: $ref: "#/components/schemas/TimelineEnvelope" "401": *a1 - "403": &a69 + "403": &a71 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled` with @@ -25777,7 +25801,7 @@ paths: schema: $ref: "#/components/schemas/TimelineReadinessEnvelope" "401": *a1 - "403": *a69 + "403": *a71 "422": *a4 "429": *a5 /api/life-events: @@ -25795,7 +25819,7 @@ paths: schema: $ref: "#/components/schemas/LifeEventListEnvelope" "401": *a1 - "403": &a70 + "403": &a72 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Owner-only in v1.42: no share @@ -25837,10 +25861,10 @@ paths: schema: $ref: "#/components/schemas/CreateLifeEventEnvelope" "401": *a1 - "403": *a70 + "403": *a72 "409": *a7 "422": *a4 - "429": *a13 + "429": *a16 /api/life-events/{id}: patch: tags: @@ -25868,8 +25892,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLifeEventEnvelope" "401": *a1 - "403": *a70 - "404": &a71 + "403": *a72 + "404": &a73 description: No such life event on this account. content: application/json: @@ -25896,8 +25920,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLifeEventEnvelope" "401": *a1 - "403": *a70 - "404": *a71 + "403": *a72 + "404": *a73 "422": *a4 "429": *a5 /api/search: @@ -25943,7 +25967,7 @@ paths: schema: $ref: "#/components/schemas/SearchEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/auth/me/ai-usage: @@ -26072,7 +26096,7 @@ paths: required: - file responses: - "202": *a39 + "202": *a41 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -26108,7 +26132,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a20 + "503": *a23 /api/anamnesis/life-circumstances: get: tags: @@ -26123,7 +26147,7 @@ paths: schema: $ref: "#/components/schemas/LifeCircumstancesEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "422": *a4 "429": *a5 /api/anamnesis/life-circumstances/ai-sections: @@ -26212,7 +26236,7 @@ paths: schema: $ref: "#/components/schemas/AuditCCreatedEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "409": *a7 "422": *a4 "429": *a5 @@ -26236,7 +26260,7 @@ paths: schema: $ref: "#/components/schemas/AuditCRemovedEnvelope" "401": *a1 - "403": *a12 + "403": *a15 "404": description: "`anamnesis.auditC.notFound`." content: @@ -26267,7 +26291,7 @@ paths: schema: $ref: "#/components/schemas/DawarichConnectEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/dawarich/status: @@ -26283,7 +26307,7 @@ paths: schema: $ref: "#/components/schemas/DawarichStatusEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/dawarich/places: @@ -26307,7 +26331,7 @@ paths: schema: $ref: "#/components/schemas/DawarichPlacesEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "409": description: "`dawarich.not_connected`." content: @@ -26345,7 +26369,7 @@ paths: schema: $ref: "#/components/schemas/DawarichSettingsEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "409": description: "`dawarich.not_connected`." content: @@ -26377,7 +26401,7 @@ paths: schema: $ref: "#/components/schemas/DawarichProbeEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 "502": @@ -26413,7 +26437,7 @@ paths: schema: $ref: "#/components/schemas/DawarichSyncEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "409": description: "`dawarich.not_connected`." content: @@ -26448,7 +26472,7 @@ paths: schema: $ref: "#/components/schemas/DawarichDisconnectEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/environment/travel/suggestions: @@ -26466,7 +26490,7 @@ paths: schema: $ref: "#/components/schemas/TravelSuggestionsEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "422": *a4 "429": *a5 /api/environment/travel/suggestions/{id}/dismiss: @@ -26489,7 +26513,7 @@ paths: schema: $ref: "#/components/schemas/TravelSuggestionDismissEnvelope" "401": *a1 - "403": *a21 + "403": *a11 "404": description: "`environment.travel_suggestion.not_found`." content: diff --git a/src/lib/openapi/routes/health-record.ts b/src/lib/openapi/routes/health-record.ts index 303055b89..ca515a797 100644 --- a/src/lib/openapi/routes/health-record.ts +++ b/src/lib/openapi/routes/health-record.ts @@ -15,10 +15,44 @@ import { import { dataEnvelope, errorEnvelope, + moduleDisabledResponse, recordRefusal, stdResponses, } from "./shared"; +// The FHIR face answers its own refusals as FHIR: a 400, 404 or 429 raised +// by a `/api/fhir/*` handler is an `OperationOutcome` served as +// `application/fhir+json` (`operationOutcome` in `@/lib/fhir/rest`), which is +// what a FHIR client parses on any non-2xx. The refusals that run before the +// handler's own body are the API's: the generic 401 / 403 envelopes and the +// 403 `module.disabled`, which keeps the errorCode every client already +// branches on for a disabled module. Declared here after `...stdResponses`, +// so the FHIR shapes replace the generic JSON ones. +const fhirOutcomeContent = { + "application/fhir+json": { + schema: z.string().meta({ format: "binary" }), + }, +}; + +const fhirDataRefusals = { + ...moduleDisabledResponse, + "429": { + description: + "Rate limit exceeded: the per-account `fhir:` bucket, 120 requests per hour, shared by every `/api/fhir/*` data route. The body is an `OperationOutcome` (`issue[0].code` = `throttled`) served as `application/fhir+json`, not the JSON error envelope. The `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers describe the bucket; back off on `Retry-After`.", + content: fhirOutcomeContent, + headers: stdResponses["429"].headers, + }, +}; + +const fhirSearchRefusals = { + ...fhirDataRefusals, + "400": { + description: + "A search parameter the server cannot honour, such as an `_offset` above 9007199254740991. The body is an `OperationOutcome` (`issue[0].code` = `invalid`, the reason in `diagnostics`) served as `application/fhir+json`.", + content: fhirOutcomeContent, + }, +}; + // The two request bodies this module publishes. `.meta()` CLONES in Zod 4 // rather than annotating in place, so the returned schema has to be captured // and referenced: a bare `schema.meta({...})` statement registers nothing and @@ -394,6 +428,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, @@ -426,6 +461,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, @@ -456,6 +492,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, @@ -486,6 +523,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, @@ -516,6 +554,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, @@ -546,6 +585,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirDataRefusals, }, }, }, @@ -576,6 +616,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirDataRefusals, }, }, }, @@ -606,6 +647,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirDataRefusals, }, }, }, @@ -636,6 +678,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirDataRefusals, }, }, }, @@ -666,6 +709,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirDataRefusals, }, }, }, @@ -696,6 +740,7 @@ export const healthRecordPaths: NonNullable = { }, }, ...stdResponses, + ...fhirSearchRefusals, }, }, }, diff --git a/tests/integration/routes/fhir-module-gate.test.ts b/tests/integration/routes/fhir-module-gate.test.ts index e018e8f66..fc5363f74 100644 --- a/tests/integration/routes/fhir-module-gate.test.ts +++ b/tests/integration/routes/fhir-module-gate.test.ts @@ -10,9 +10,6 @@ * record leaves; the static CapabilityStatement carries no user data and * stays reachable. The module is switched the way the settings switch it, * through the stored per-account preferences. - * - * The registry declares no 403 for these operations; the refusals below skip - * the contract check for that reason. */ import { beforeEach, describe, expect, it } from "vitest"; @@ -45,9 +42,6 @@ import { getPrismaClient, truncateAllTables } from "../setup"; const prisma = getPrismaClient(); const INSURANCE_NUMBER = "A123456789"; -const MODULE_OFF_SKIP = - "the FHIR operations declare no 403; the module gate's module.disabled envelope is undocumented on them"; - type Handler = Parameters[0]; /** Every data route, by the path a caller hits; the reads name a held id. */ @@ -135,7 +129,7 @@ async function seedOwner(doctorReport: boolean) { return user; } -async function callEach(auth: RouteAuth, skip: boolean) { +async function callEach(auth: RouteAuth) { const results = []; for (const { handler, route, params } of DATA_ROUTES) { const res = await callRoute(handler, { @@ -143,7 +137,6 @@ async function callEach(auth: RouteAuth, skip: boolean) { route, params, auth, - contract: skip ? { skip: MODULE_OFF_SKIP } : undefined, }); results.push({ route, res }); } @@ -158,7 +151,7 @@ describe("/api/fhir/* and the doctorReport module", () => { await makeToken(owner, { permissions: [FHIR_READ_SCOPE] }), await makeSession(owner), ]) { - for (const { route, res } of await callEach(auth, true)) { + for (const { route, res } of await callEach(auth)) { expect(res.status, route).toBe(403); expect(res.data, route).toBeNull(); expect(res.meta, route).toMatchObject({ @@ -177,7 +170,7 @@ describe("/api/fhir/* and the doctorReport module", () => { const owner = await seedOwner(true); const auth = await makeToken(owner, { permissions: [FHIR_READ_SCOPE] }); - for (const { route, res } of await callEach(auth, false)) { + for (const { route, res } of await callEach(auth)) { expect(res.status, route).toBe(200); expect(res.headers.get("content-type"), route).toContain( "application/fhir+json", diff --git a/tests/integration/routes/fhir.test.ts b/tests/integration/routes/fhir.test.ts index 03133023c..a094784b8 100644 --- a/tests/integration/routes/fhir.test.ts +++ b/tests/integration/routes/fhir.test.ts @@ -758,9 +758,6 @@ describe("FHIR paging", () => { route: "/api/fhir/Observation", query: { _offset: "1e20" }, auth, - contract: { - skip: "the registry declares no 400 on the FHIR searches, though the description names the 400 OperationOutcome", - }, }); expect(res.status).toBe(400); @@ -784,12 +781,11 @@ describe("FHIR paging", () => { method: "GET", route: "/api/fhir/Patient", auth, - contract: { - skip: "the registry declares the shared 429 as an application/json error envelope; the FHIR routes answer an application/fhir+json OperationOutcome", - }, }); expect(res.status).toBe(429); + expect(res.headers.get("content-type")).toContain("application/fhir+json"); + expect(Number(res.headers.get("retry-after"))).toBeGreaterThanOrEqual(1); expect(outcomeSchema.parse(res.body).issue[0].code).toBe("throttled"); expect(JSON.stringify(res.body)).not.toContain("Sample Patient"); }); From 579789ec386b02e5a724da388ee66eef359921bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:07:46 +0200 Subject: [PATCH 07/14] fix(api): declare the 400 and 415 the single-value profile writes answer The six /api/auth/me scalar writes (unit preference, glucose unit, automatic document reading, local lab OCR, workout routes, the central Codex switch) read their body through safeJson with a 1 KB cap, so each answers 415 for a body that is not JSON by content type and 400 for one that does not parse, beside the 413 they already declared. Their descriptions said so and the responses did not. All three now come from one block. The modules and disable-coach route tests had been skipping the contract check over a 400 and a 409 the registry already declares; the skips are gone. --- docs/api/openapi.yaml | 535 +++++++++--------- src/lib/openapi/routes/profile.ts | 53 +- .../routes/auth-me-disable-coach.test.ts | 3 - .../routes/auth-me-modules.test.ts | 23 +- .../routes/auth-me-unit-preference.test.ts | 6 - 5 files changed, 300 insertions(+), 320 deletions(-) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 8638f44b0..e6762c399 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -9631,14 +9631,26 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchUnitPreferenceEnvelope" + "400": &a21 + description: The request body is not parseable JSON. Nothing was written. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "413": + "413": &a22 description: Request body exceeds 1024 bytes. Nothing was written. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "415": &a23 + description: "The request did not carry `Content-Type: application/json`. Nothing was read and nothing was written." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 /api/auth/me/glucose-unit: @@ -9689,14 +9701,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchGlucoseUnitEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/documents-auto-ai-read: @@ -9752,14 +9761,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchDocumentsAutoAiReadEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/injection-site-prefs: @@ -10061,14 +10067,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchLabsLocalOcrEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/workout-routes: @@ -10109,14 +10112,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchWorkoutRoutesEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/use-central-codex: @@ -10162,14 +10162,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchUseCentralCodexEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/locale: @@ -10376,7 +10373,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a23 + "503": &a26 description: "The AI capability could not be resolved (its inputs could not be read) and the answer failed closed: `meta.errorCode` = `ai.unavailable`, `meta.reason` = `check_failed`. Retry later; no data depends on it." content: @@ -11592,7 +11589,7 @@ paths: $ref: "#/components/schemas/CorrectAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": &a21 + "404": &a24 description: No current revision with this id exists for the authenticated account. content: application/json: @@ -11629,7 +11626,7 @@ paths: $ref: "#/components/schemas/RemoveAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": *a21 + "404": *a24 "409": description: Anamnesis fact changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -12168,7 +12165,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteList" "401": *a1 - "403": &a22 + "403": &a25 description: The session is valid but its account is not an administrator. `meta.errorCode` = `auth.admin.required`. content: application/json: @@ -12209,7 +12206,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteCreated" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 /api/admin/invites/{id}: @@ -12234,7 +12231,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteRevoked" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 /api/insights/chat/messages/{id}/feedback: @@ -12290,7 +12287,7 @@ paths: schema: $ref: "#/components/schemas/AdminMeasurementMaintenanceStatusResponse" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 post: @@ -12601,7 +12598,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a23 + "503": *a26 post: tags: - Insights @@ -14062,7 +14059,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagPatchEnvelope" "401": *a1 "403": *a9 - "404": &a24 + "404": &a27 description: Not found / not owned by the caller. content: application/json: @@ -14098,7 +14095,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/{key}/hidden: @@ -14135,7 +14132,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/groups: @@ -14190,7 +14187,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupPatchEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 delete: @@ -14222,7 +14219,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/layout: @@ -14390,7 +14387,7 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "409": &a25 + "409": &a28 description: A mood entry with the same `(date, moodLoggedAt)` instant already exists. `meta.errorCode` = `mood.duplicate_timestamp`. Only a request without `externalId` can answer this — with one, the write upserts in place. @@ -14422,7 +14419,7 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 put: @@ -14457,8 +14454,8 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "404": *a24 - "409": *a25 + "404": *a27 + "409": *a28 "422": *a4 "429": *a5 delete: @@ -14484,7 +14481,7 @@ paths: $ref: "#/components/schemas/MoodEntryDeleteEnvelope" "401": *a1 "403": *a15 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood-entries/restore: @@ -14635,7 +14632,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutNtfySettingsResponse" - "400": &a26 + "400": &a29 description: Body is not parseable JSON. content: application/json: @@ -14650,13 +14647,13 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": &a27 + "413": &a30 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a28 + "415": &a31 description: Content-Type is not `application/json`. content: application/json: @@ -14717,7 +14714,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutTelegramSettingsResponse" - "400": *a26 + "400": *a29 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -14733,8 +14730,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": *a27 - "415": *a28 + "413": *a30 + "415": *a31 "422": description: Body matched neither accepted shape; or `enabled` is true without both a bot token and a chat id; or the deployment's `APP_URL` is not a public HTTPS host on port 80/88/443/8443; or Telegram refused the webhook @@ -14789,11 +14786,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PutWebhookSettingsResponse" - "400": *a26 + "400": *a29 "401": *a1 "403": *a9 - "413": *a27 - "415": *a28 + "413": *a30 + "415": *a31 "422": description: Body matched neither accepted shape, or `enabled` is true while no webhook URL is stored; or the URL is on a private network the operator has not listed in `NOTIFICATION_PRIVATE_ORIGINS` (`meta.errorCode` = @@ -15458,7 +15455,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentLatestForKindEnvelope" - $ref: "#/components/schemas/ConsentLatestByKindEnvelope" - "400": &a29 + "400": &a32 description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`, `ai_extraction`. Multi-issue envelope; nothing was read or revoked. The consent family answers 400 here where the rest of the API answers 422." @@ -15519,7 +15516,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentRevokeForKindEnvelope" - $ref: "#/components/schemas/ConsentRevokeAllEnvelope" - "400": *a29 + "400": *a32 "401": *a1 "403": *a9 "422": *a4 @@ -16312,7 +16309,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderEnvelope" "401": *a1 "403": *a15 - "404": &a30 + "404": &a33 description: Measurement reminder not found / not owned. content: application/json: @@ -16350,7 +16347,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderPatchEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 delete: @@ -16373,7 +16370,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderDeleteEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/satisfy: @@ -16398,7 +16395,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSatisfyEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/complete: @@ -16426,7 +16423,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderCompleteEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/skip: @@ -16456,7 +16453,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSkipEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/snooze: @@ -16490,7 +16487,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSnoozeEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/history: @@ -16531,7 +16528,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/labs: @@ -16693,7 +16690,7 @@ paths: $ref: "#/components/schemas/GetLabResultResponse" "401": *a1 "403": *a15 - "404": &a31 + "404": &a34 description: Lab result not found (or owned by another user). content: application/json: @@ -16728,7 +16725,7 @@ paths: $ref: "#/components/schemas/UpdateLabResultResponse" "401": *a1 "403": *a15 - "404": *a31 + "404": *a34 "422": *a4 "429": *a5 delete: @@ -16751,7 +16748,7 @@ paths: $ref: "#/components/schemas/DeleteLabResultResponse" "401": *a1 "403": *a15 - "404": *a31 + "404": *a34 "422": *a4 "429": *a5 /api/labs/restore: @@ -16839,7 +16836,7 @@ paths: schema: $ref: "#/components/schemas/OcrTextExtractRequest" responses: - "202": &a41 + "202": &a44 description: Queued for the background worker. Every refusal the route can answer quickly (capability, provider, rate limit, budget, validation) has already been answered; anything that goes wrong from here is reported on the run. @@ -17020,7 +17017,7 @@ paths: $ref: "#/components/schemas/GetBiomarkerResponse" "401": *a1 "403": *a15 - "404": &a32 + "404": &a35 description: "Biomarker not found (or owned by another user). `meta.errorCode` = `labs.biomarker.notFound`. Since v1.43 a marker that was merged into another answers with `meta.mergedInto` naming the marker it went into: drop the cached marker and open that one." @@ -17062,7 +17059,7 @@ paths: $ref: "#/components/schemas/UpdateBiomarkerResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "409": description: "`labs.biomarker.duplicate`: a rename collides with another of the caller's markers. `labs.biomarker.alias.taken` (v1.43): one of `aliases` is already the name or an alias of another marker; @@ -17096,7 +17093,7 @@ paths: $ref: "#/components/schemas/DeleteBiomarkerResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "422": *a4 "429": *a5 /api/biomarkers/{id}/merge: @@ -17134,7 +17131,7 @@ paths: $ref: "#/components/schemas/MergeBiomarkersResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "409": *a7 "422": description: "`labs.biomarker.merge.invalid`: a malformed body, or the target among the sources (Zod issues in @@ -17359,7 +17356,7 @@ paths: $ref: "#/components/schemas/GetEncounterEnvelope" "401": *a1 "403": *a15 - "404": &a33 + "404": &a36 description: Visit not found (or owned by another user). content: application/json: @@ -17393,7 +17390,7 @@ paths: $ref: "#/components/schemas/UpdateEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 delete: @@ -17418,7 +17415,7 @@ paths: $ref: "#/components/schemas/DeleteEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/encounters/{id}/restore: @@ -17444,7 +17441,7 @@ paths: $ref: "#/components/schemas/RestoreEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/encounters/{id}/links: @@ -17476,7 +17473,7 @@ paths: $ref: "#/components/schemas/LinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 delete: @@ -17506,7 +17503,7 @@ paths: $ref: "#/components/schemas/UnlinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/practitioners: @@ -17595,7 +17592,7 @@ paths: $ref: "#/components/schemas/GetPractitionerEnvelope" "401": *a1 "403": *a15 - "404": &a34 + "404": &a37 description: Practitioner not found (or owned by another user). content: application/json: @@ -17629,7 +17626,7 @@ paths: $ref: "#/components/schemas/UpdatePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 delete: @@ -17654,7 +17651,7 @@ paths: $ref: "#/components/schemas/DeletePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 /api/practitioners/{id}/restore: @@ -17680,7 +17677,7 @@ paths: $ref: "#/components/schemas/RestorePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 /api/vaccinations/custom: @@ -17724,7 +17721,7 @@ paths: $ref: "#/components/schemas/CreateCustomVaccineEnvelope" "401": *a1 "403": *a15 - "409": &a35 + "409": &a38 description: A live definition on the record already carries this name, compared without case (`vaccination.custom.name-taken`). content: @@ -17761,13 +17758,13 @@ paths: $ref: "#/components/schemas/UpdateCustomVaccineEnvelope" "401": *a1 "403": *a15 - "404": &a36 + "404": &a39 description: No live definition with this id on the record (`vaccination.custom.not-found`). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a35 + "409": *a38 "422": *a4 "429": *a5 delete: @@ -17792,7 +17789,7 @@ paths: $ref: "#/components/schemas/DeleteCustomVaccineEnvelope" "401": *a1 "403": *a15 - "404": *a36 + "404": *a39 "422": *a4 "429": *a5 /api/vaccinations: @@ -17904,7 +17901,7 @@ paths: $ref: "#/components/schemas/GetVaccinationEnvelope" "401": *a1 "403": *a15 - "404": &a37 + "404": &a40 description: Vaccination not found (or owned by another user). content: application/json: @@ -17939,7 +17936,7 @@ paths: $ref: "#/components/schemas/UpdateVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 delete: @@ -17964,7 +17961,7 @@ paths: $ref: "#/components/schemas/DeleteVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/{id}/restore: @@ -17989,7 +17986,7 @@ paths: $ref: "#/components/schemas/RestoreVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/{id}/booster: @@ -18057,7 +18054,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/suggest: @@ -18118,7 +18115,7 @@ paths: $ref: "#/components/schemas/LinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 delete: @@ -18148,7 +18145,7 @@ paths: $ref: "#/components/schemas/UnlinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/illness/episodes: @@ -18263,7 +18260,7 @@ paths: $ref: "#/components/schemas/GetIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": &a38 + "404": &a41 description: Illness episode not found (or owned by another user). content: application/json: @@ -18298,7 +18295,7 @@ paths: $ref: "#/components/schemas/UpdateIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 delete: @@ -18322,7 +18319,7 @@ paths: $ref: "#/components/schemas/DeleteIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/restore: @@ -18350,7 +18347,7 @@ paths: $ref: "#/components/schemas/RestoreIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "409": *a7 "422": *a4 "429": *a5 @@ -18383,7 +18380,7 @@ paths: $ref: "#/components/schemas/ResolveIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/day-logs: @@ -18434,7 +18431,7 @@ paths: $ref: "#/components/schemas/GetIllnessDayLogEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 post: @@ -18471,7 +18468,7 @@ paths: $ref: "#/components/schemas/UpsertIllnessDayLogCreatedEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/correlation: @@ -18499,7 +18496,7 @@ paths: $ref: "#/components/schemas/IllnessCorrelationEnvelope" "401": *a1 "403": *a9 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/insights: @@ -18716,7 +18713,7 @@ paths: $ref: "#/components/schemas/GetAllergyEnvelope" "401": *a1 "403": *a15 - "404": &a39 + "404": &a42 description: Allergy not found (or owned by another user). content: application/json: @@ -18750,7 +18747,7 @@ paths: $ref: "#/components/schemas/UpdateAllergyEnvelope" "401": *a1 "403": *a15 - "404": *a39 + "404": *a42 "422": *a4 "429": *a5 delete: @@ -18774,7 +18771,7 @@ paths: $ref: "#/components/schemas/DeleteAllergyEnvelope" "401": *a1 "403": *a15 - "404": *a39 + "404": *a42 "422": *a4 "429": *a5 /api/family-history: @@ -18848,7 +18845,7 @@ paths: $ref: "#/components/schemas/GetFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": &a40 + "404": &a43 description: Family history entry not found (or owned by another user). content: application/json: @@ -18882,7 +18879,7 @@ paths: $ref: "#/components/schemas/UpdateFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a40 + "404": *a43 "422": *a4 "429": *a5 delete: @@ -18906,7 +18903,7 @@ paths: $ref: "#/components/schemas/DeleteFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a40 + "404": *a43 "422": *a4 "429": *a5 /api/documents/inbound: @@ -19579,7 +19576,7 @@ paths: required: true schema: type: string - - &a42 + - &a45 name: Prefer in: header required: false @@ -19604,9 +19601,9 @@ paths: application/json: schema: $ref: "#/components/schemas/InboundExtractEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": &a43 + "403": &a46 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and @@ -19632,7 +19629,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a44 + "503": &a47 description: "`ai.unavailable` — the inputs behind the AI capability could not be read, and the answer failed closed. Nothing was sent. Retry later." content: @@ -19662,7 +19659,7 @@ paths: required: true schema: type: string - - *a42 + - *a45 requestBody: required: false content: @@ -19698,12 +19695,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSuggestEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/summary: post: tags: @@ -19753,7 +19750,7 @@ paths: type: boolean default: false description: With `persist=true`, explicitly replace an existing stored summary. - - *a42 + - *a45 requestBody: required: false content: @@ -19789,12 +19786,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSummaryEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/index: post: tags: @@ -19820,7 +19817,7 @@ paths: required: true schema: type: string - - *a42 + - *a45 requestBody: required: false content: @@ -19856,12 +19853,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentIndexEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/chat: post: tags: @@ -19901,10 +19898,10 @@ paths: type: string description: "SSE frames: `data: \\n\\n`. See the operation description for the per-`type` frame shapes." "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 get: tags: - Documents @@ -19968,10 +19965,10 @@ paths: schema: $ref: "#/components/schemas/DocumentReindexEnvelope" "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/facts/{factId}: patch: tags: @@ -20081,7 +20078,7 @@ paths: $ref: "#/components/schemas/CreateCustomMetricResponse" "401": *a1 "403": *a9 - "409": &a46 + "409": &a49 description: A custom metric with this name already exists for the caller. content: application/json: @@ -20110,7 +20107,7 @@ paths: $ref: "#/components/schemas/GetCustomMetricResponse" "401": *a1 "403": *a15 - "404": &a45 + "404": &a48 description: Custom metric not found (or owned by another user). content: application/json: @@ -20145,8 +20142,8 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a45 - "409": *a46 + "404": *a48 + "409": *a49 "422": *a4 "429": *a5 delete: @@ -20170,7 +20167,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a45 + "404": *a48 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries: @@ -20214,7 +20211,7 @@ paths: $ref: "#/components/schemas/ListCustomMetricEntriesEnvelope" "401": *a1 "403": *a15 - "404": *a45 + "404": *a48 "422": *a4 "429": *a5 post: @@ -20244,7 +20241,7 @@ paths: $ref: "#/components/schemas/CreateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a45 + "404": *a48 "409": *a7 "422": *a4 "429": *a16 @@ -20281,7 +20278,7 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": &a47 + "404": &a50 description: Custom metric entry not found (or owned by another user). content: application/json: @@ -20315,7 +20312,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a47 + "404": *a50 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries/restore: @@ -20623,7 +20620,7 @@ paths: schema: $ref: "#/components/schemas/NutrientCatalogEnvelope" "401": *a1 - "403": &a48 + "403": &a51 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is @@ -20772,7 +20769,7 @@ paths: schema: $ref: "#/components/schemas/NutrientOverviewEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "422": *a4 "429": *a5 /api/nutrients/daily: @@ -20809,7 +20806,7 @@ paths: schema: $ref: "#/components/schemas/NutrientDailySeriesEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "422": *a4 "429": *a5 /api/nutrients/water: @@ -20838,7 +20835,7 @@ paths: schema: $ref: "#/components/schemas/NutrientWaterWriteEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "409": *a7 "422": *a4 "429": *a5 @@ -21359,7 +21356,7 @@ paths: application/json: schema: $ref: "#/components/schemas/WithingsCredentialsUpdateEnvelope" - "400": &a49 + "400": &a52 description: Body is not parseable JSON. content: application/json: @@ -21367,19 +21364,19 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "413": &a50 + "413": &a53 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a51 + "415": &a54 description: Content-Type is not `application/json`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a52 + "422": &a55 description: "`clientId` and `clientSecret` are both required and each must be 1–200 characters. The envelope carries a single fixed message, not per-field issues." content: @@ -21570,12 +21567,12 @@ paths: application/json: schema: $ref: "#/components/schemas/FitbitCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21730,12 +21727,12 @@ paths: application/json: schema: $ref: "#/components/schemas/GoogleHealthCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21901,12 +21898,12 @@ paths: application/json: schema: $ref: "#/components/schemas/OuraCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21986,21 +21983,21 @@ paths: $ref: "#/components/schemas/OuraTestEnvelope" "401": *a1 "403": *a9 - "422": &a53 + "422": &a56 description: No usable connection for this provider — nothing stored, or the stored grant could no longer be resolved into an access token. `meta.errorCode` = `not_configured`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a54 + "429": &a57 description: More than 5 requests in 60 s from this user. `meta.errorCode` = `rate_limited_self` — this is the local limiter, distinct from a `rate_limited` 502 which is the provider's. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": &a55 + "502": &a58 description: "The probe did not complete. `meta.errorCode` names the class: `credentials_rejected` (the provider answered 401/403 — the stored grant is gone), `rate_limited` (the PROVIDER answered 429, which is not this endpoint's own limiter), `upstream_error` (5xx), `timeout` (no answer within 5 s), `connection_failed` @@ -22046,12 +22043,12 @@ paths: application/json: schema: $ref: "#/components/schemas/PolarCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -22131,9 +22128,9 @@ paths: $ref: "#/components/schemas/PolarTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/strava/credentials: get: tags: @@ -22170,12 +22167,12 @@ paths: application/json: schema: $ref: "#/components/schemas/StravaCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -22257,9 +22254,9 @@ paths: $ref: "#/components/schemas/StravaTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/status: get: tags: @@ -22297,7 +22294,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/fitbit/test: post: tags: @@ -22323,9 +22320,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/google-health/resume: post: tags: @@ -22343,7 +22340,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/google-health/test: post: tags: @@ -22377,9 +22374,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/whoop/resume: post: tags: @@ -22397,7 +22394,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/whoop/test: post: tags: @@ -22415,9 +22412,9 @@ paths: $ref: "#/components/schemas/WhoopIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/withings/resume: post: tags: @@ -22436,7 +22433,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/withings/test: post: tags: @@ -22455,8 +22452,8 @@ paths: $ref: "#/components/schemas/WithingsIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 + "422": *a56 + "429": *a57 "502": description: "The probe did not complete. The shared codes apply (`credentials_rejected`, `rate_limited`, `upstream_error`, `timeout`, `connection_failed`) and this path adds one of its own: @@ -22874,7 +22871,7 @@ paths: schema: $ref: "#/components/schemas/RecordActivityEnvelope" "401": *a1 - "403": &a56 + "403": &a59 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused. `meta.errorCode` is `sharing.not_permitted` when the request was made while acting on another account (grant management is never @@ -22901,7 +22898,7 @@ paths: schema: $ref: "#/components/schemas/AccountGrantListEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "422": *a4 "429": *a5 post: @@ -23005,7 +23002,7 @@ paths: schema: $ref: "#/components/schemas/AcceptedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such invitation for this account. content: @@ -23049,7 +23046,7 @@ paths: schema: $ref: "#/components/schemas/RevokedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such grant on this account's record. content: @@ -23090,7 +23087,7 @@ paths: schema: $ref: "#/components/schemas/RenouncedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such grant held by this account. content: @@ -23482,7 +23479,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "409": description: "The record this browser session is on is not the record the request asserted. `meta.errorCode` is `sharing.session.changed`. It is not a refusal of access — the caller may well be entitled to both records — @@ -23871,7 +23868,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionListEnvelope" "401": *a1 - "403": &a57 + "403": &a60 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API instance-wide. Every route in this module refuses while that setting is off, for a cookie session as well as @@ -23905,7 +23902,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionRevokeEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "404": description: No live connection with this id for this caller — also the answer for an already-revoked one, so a repeat revoke is 404 rather than an idempotent 200. @@ -23932,7 +23929,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenListEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "422": *a4 "429": *a5 post: @@ -23965,7 +23962,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "413": description: Body exceeds 16 KiB. content: @@ -24008,7 +24005,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenRevokeEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "404": description: No `health:read` token with this id for this caller. An already-revoked token is still found, so a repeat revoke answers 200. @@ -24070,7 +24067,7 @@ paths: record (audited). Fitbit is not offered: its classic API is retired. Cookie session, while acting on a managed record the caller guards." parameters: - - &a58 + - &a61 name: provider in: path required: true @@ -24117,7 +24114,7 @@ paths: Audited against the record with the Guardian as the actor. Cookie session or Bearer, while acting on a managed record the caller guards." parameters: - - *a58 + - *a61 responses: "200": description: The provider is disconnected. @@ -24143,7 +24140,7 @@ paths: description: Returns the named family's resolved settings for the record the session is acting on. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - &a59 + - &a62 name: family in: path required: true @@ -24187,7 +24184,7 @@ paths: ride the audit row. Body capped at 64 KiB. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - *a59 + - *a62 requestBody: required: true content: @@ -24814,13 +24811,13 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupDocument" - "403": &a60 + "403": &a63 description: Caller is not an admin. `requireAdmin()` is cookie-only, so a Bearer token lands here whatever its scope. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": &a61 + "404": &a64 description: No stored copy with this id. content: application/json: @@ -24894,8 +24891,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "403": *a60 - "404": *a61 + "403": *a63 + "404": *a64 "409": description: A restore of the same account is already queued or running (`meta.errorCode` = `backup.restore.active`, `meta.jobId` names it), the stored copy was replaced by a newer one while it was being checked @@ -24939,7 +24936,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobListResponse" - "403": *a60 + "403": *a63 /api/admin/backups/restores/{jobId}: get: tags: @@ -24962,7 +24959,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobResponse" - "403": *a60 + "403": *a63 "404": description: No restore job with this id. content: @@ -24981,7 +24978,7 @@ paths: request leaves the server. Needs neither the Documents module nor the list: with the list unset, `available` is false and any connection still stored is listed (with `originAllowed: false`) so it can be removed. Cookie session only; not delegable." - security: &a62 + security: &a65 - cookieAuth: [] responses: "200": @@ -24991,7 +24988,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourcesStatusEnvelope" "401": *a1 - "403": &a63 + "403": &a66 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused before any request to the source: `documents.sources.browserOnly` for a Bearer caller, `sharing.not_permitted` while acting on another @@ -25016,9 +25013,9 @@ paths: `documents.sources.tokenRequired`), so a stored token is never sent to an address it was not saved for. A Papra organization id is letters, digits, `_` and `-`. The token is stored encrypted (AES-256-GCM) and never returned. Ten saves and tests a minute per person. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - &a64 + - &a67 name: system in: path required: true @@ -25057,8 +25054,8 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceConnectionEnvelope" "401": *a1 - "403": *a63 - "404": &a65 + "403": *a66 + "404": &a68 description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. `documents.sources.notFound`: the source has no such document, or (Papra) no such organization, including @@ -25077,7 +25074,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": &a66 + "502": &a69 description: "The source could not answer usefully, with the reason in `meta.errorCode` and, when the source answered at all, its HTTP status in `meta.upstreamStatus` (its body is never passed on): `documents.sources.unreachable` (no connection, timeout, or a destination refused at dial time), `documents.sources.redirected` (the source @@ -25096,9 +25093,9 @@ paths: description: Deletes the caller's connection for this system, and its token with it. Works even after the operator removed the origin from the list, and with the Documents module switched off. `disconnected` is false when there was nothing to delete. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: Removed, or there was nothing to remove. @@ -25107,7 +25104,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceDisconnectEnvelope" "401": *a1 - "403": *a63 + "403": *a66 "422": *a4 "429": *a5 /api/documents/sources/{system}/test: @@ -25118,9 +25115,9 @@ paths: description: One request to the source with the stored token. Answers `ok` with the round-trip time and stamps `lastVerifiedAt`, or the reason in `meta.errorCode`. Shares the save's allowance of ten a minute. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: The source answered. @@ -25129,9 +25126,9 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTestEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": &a67 + "403": *a66 + "404": *a68 + "422": &a70 description: "`documents.sources.originNotAllowed`: the base address's origin is not on the operator's list (checked on every request, so a connection saved earlier is cut off once its origin is removed), or another validation failure." @@ -25140,7 +25137,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/tags: get: tags: @@ -25148,9 +25145,9 @@ paths: summary: Tags of a connected archive description: The source's tags for the picker's filter, the first 250 by name, in the source's own ids. Shares the search allowance of sixty a minute. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: The tags. @@ -25159,11 +25156,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTagsEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": *a67 + "403": *a66 + "404": *a68 + "422": *a70 "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/search: get: tags: @@ -25177,9 +25174,9 @@ paths: under that source key: `new`, `imported` with the vault document's `documentId`, or `deleted` (deleted in HealthLog; importing it again stores nothing). Nothing is stored. Sixty searches a minute, shared with the tag read. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 - name: q in: query required: false @@ -25220,11 +25217,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceSearchEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": *a67 + "403": *a66 + "404": *a68 + "422": *a70 "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/import: post: tags: @@ -25243,9 +25240,9 @@ paths: fetched), including a duplicate. `kind` sets the document type (default OTHER). Automatic AI reading follows the person's own setting. Counts against the person's upload allowance (60 an hour) and storage quota. 201 for a new document, 200 otherwise. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 requestBody: required: true content: @@ -25303,7 +25300,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceImportEnvelope" "401": *a1 - "403": *a63 + "403": *a66 "404": description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. @@ -25334,7 +25331,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a67 + "422": *a70 "429": description: "`documents.inbound.rateLimited` (the person's hourly upload allowance, with the rate-limit headers) or `documents.inbound.uploadBusy` (too many uploads in progress; `Retry-After: 1`)." @@ -25342,7 +25339,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": *a66 + "502": *a69 /api/symptoms/definitions: get: tags: @@ -25366,7 +25363,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomDefinitionsEnvelope" "401": *a1 - "403": &a68 + "403": &a71 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `illness.disabled`: the record @@ -25410,7 +25407,7 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 + "403": *a71 "409": *a7 "422": *a4 "429": *a5 @@ -25440,8 +25437,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 - "404": &a69 + "403": *a71 + "404": &a72 description: "`symptoms.definition.notFound`: no definition with this id on the record." content: application/json: @@ -25475,8 +25472,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 - "404": *a69 + "403": *a71 + "404": *a72 "422": *a4 "429": *a5 /api/symptoms/events: @@ -25525,7 +25522,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomEventsEnvelope" "401": *a1 - "403": *a68 + "403": *a71 "422": *a4 "429": *a5 post: @@ -25551,8 +25548,8 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": *a69 + "403": *a71 + "404": *a72 "409": *a7 "422": *a4 "429": *a5 @@ -25582,8 +25579,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": &a70 + "403": *a71 + "404": &a73 description: "`symptoms.event.notFound`: no occurrence with this id on the record." content: application/json: @@ -25610,8 +25607,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": *a70 + "403": *a71 + "404": *a73 "422": *a4 "429": *a5 /api/day/index: @@ -25762,7 +25759,7 @@ paths: schema: $ref: "#/components/schemas/TimelineEnvelope" "401": *a1 - "403": &a71 + "403": &a74 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled` with @@ -25801,7 +25798,7 @@ paths: schema: $ref: "#/components/schemas/TimelineReadinessEnvelope" "401": *a1 - "403": *a71 + "403": *a74 "422": *a4 "429": *a5 /api/life-events: @@ -25819,7 +25816,7 @@ paths: schema: $ref: "#/components/schemas/LifeEventListEnvelope" "401": *a1 - "403": &a72 + "403": &a75 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Owner-only in v1.42: no share @@ -25861,7 +25858,7 @@ paths: schema: $ref: "#/components/schemas/CreateLifeEventEnvelope" "401": *a1 - "403": *a72 + "403": *a75 "409": *a7 "422": *a4 "429": *a16 @@ -25892,8 +25889,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLifeEventEnvelope" "401": *a1 - "403": *a72 - "404": &a73 + "403": *a75 + "404": &a76 description: No such life event on this account. content: application/json: @@ -25920,8 +25917,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLifeEventEnvelope" "401": *a1 - "403": *a72 - "404": *a73 + "403": *a75 + "404": *a76 "422": *a4 "429": *a5 /api/search: @@ -26096,7 +26093,7 @@ paths: required: - file responses: - "202": *a41 + "202": *a44 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -26132,7 +26129,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a23 + "503": *a26 /api/anamnesis/life-circumstances: get: tags: diff --git a/src/lib/openapi/routes/profile.ts b/src/lib/openapi/routes/profile.ts index a510c4fad..a19f2eb14 100644 --- a/src/lib/openapi/routes/profile.ts +++ b/src/lib/openapi/routes/profile.ts @@ -1141,6 +1141,29 @@ const profileSummaryResponse = z "The bounded profile view a record viewer gets: allergies, family history and current lifestyle facts. Structurally incapable of becoming a Settings or AI surface.", }); +/** + * What the single-value `/api/auth/me/*` writes answer for a body they refuse + * to read. Each reads through `safeJson` with a 1 KB cap, so the three + * refusals are the cap's, not the route's: a wrong content type is 415, a body + * over the cap 413, and one that does not parse 400. All three are answered + * before validation, and nothing is written. + */ +const cappedScalarBodyRefusals = { + "400": { + description: "The request body is not parseable JSON. Nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "413": { + description: "Request body exceeds 1024 bytes. Nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "415": { + description: + "The request did not carry `Content-Type: application/json`. Nothing was read and nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, +}; + export const profilePaths: NonNullable = { "/api/profile/summary": { get: { @@ -1863,10 +1886,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -1917,10 +1937,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -1971,10 +1988,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2257,10 +2271,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2307,10 +2318,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2360,10 +2368,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, diff --git a/tests/integration/routes/auth-me-disable-coach.test.ts b/tests/integration/routes/auth-me-disable-coach.test.ts index fff8b24d3..26a75e251 100644 --- a/tests/integration/routes/auth-me-disable-coach.test.ts +++ b/tests/integration/routes/auth-me-disable-coach.test.ts @@ -174,9 +174,6 @@ describe("PATCH /api/auth/me/disable-coach", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 400 for this PATCH, which answers one for a body that is not JSON", - }, }); expect(res.status).toBe(400); expect(res.error).toBe("Invalid JSON body"); diff --git a/tests/integration/routes/auth-me-modules.test.ts b/tests/integration/routes/auth-me-modules.test.ts index db2c39337..7e9faec7d 100644 --- a/tests/integration/routes/auth-me-modules.test.ts +++ b/tests/integration/routes/auth-me-modules.test.ts @@ -37,11 +37,6 @@ type ModulesResponse = { updatedAt?: string; }; -const NO_400 = - "the registry declares no 400 for PATCH /api/auth/me/modules although the handler answers one for a body that is not JSON"; -const NO_409 = - "the registry declares no 409 for PATCH /api/auth/me/modules although the handler answers modules_conflict for a stale base token"; - beforeEach(() => truncateAllTables(prisma)); async function storedPrefs(userId: string): Promise { @@ -63,14 +58,12 @@ async function storedUpdatedAt(userId: string): Promise { async function patch( auth: Awaited>, body: Record, - contract?: { skip: string }, ) { return callRoute(PATCH, { method: "PATCH", route: ROUTE, body, auth, - contract, }); } @@ -229,7 +222,6 @@ describe("PATCH /api/auth/me/modules", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { skip: NO_400 }, }); expect(res.status).toBe(400); expect(res.error).toBe("Invalid JSON body"); @@ -281,11 +273,10 @@ describe("PATCH /api/auth/me/modules evicts the cached score", () => { const user = await makeUser(); caches.analytics.set(`${user.id}|score`, { score: 71 }); - const res = await patch( - await makeSession(user), - { sleep: false, baseUpdatedAt: "2020-01-01T00:00:00.000Z" }, - { skip: NO_409 }, - ); + const res = await patch(await makeSession(user), { + sleep: false, + baseUpdatedAt: "2020-01-01T00:00:00.000Z", + }); expect(res.status).toBe(409); expect(caches.analytics.get(`${user.id}|score`)).toEqual({ score: 71 }); }); @@ -320,11 +311,7 @@ describe("PATCH /api/auth/me/modules optimistic concurrency", () => { const first = await patch(session, { mood: false, baseUpdatedAt: base }); expect(first.status).toBe(200); - const second = await patch( - session, - { sleep: false, baseUpdatedAt: base }, - { skip: NO_409 }, - ); + const second = await patch(session, { sleep: false, baseUpdatedAt: base }); expect(second.status).toBe(409); expect(second.meta?.errorCode).toBe("modules_conflict"); expect(await storedPrefs(user.id)).toEqual({ mood: false }); diff --git a/tests/integration/routes/auth-me-unit-preference.test.ts b/tests/integration/routes/auth-me-unit-preference.test.ts index c9abbc0fd..9c9aa5cec 100644 --- a/tests/integration/routes/auth-me-unit-preference.test.ts +++ b/tests/integration/routes/auth-me-unit-preference.test.ts @@ -171,9 +171,6 @@ describe("PATCH /api/auth/me/unit-preference", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 400 for this PATCH although the description names it", - }, }); expect(res.status).toBe(400); expect(await storedPreference(user.id)).toBe("metric"); @@ -199,9 +196,6 @@ describe("PATCH /api/auth/me/unit-preference", () => { body: "unitPreference=imperial", headers: { "content-type": "application/x-www-form-urlencoded" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 415 for this PATCH although the description names it", - }, }); expect(res.status).toBe(415); expect(await storedPreference(user.id)).toBe("metric"); From d396945f443c884b9f212f8fa0cb4c5b4aa4b467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:08:02 +0200 Subject: [PATCH 08/14] fix(api): publish the backup upload POST /api/admin/backups/upload stores a backup file as a new copy, the copy whose id the published restore takes, and it refuses a file for reasons an operator has to be able to look up: a key this server does not hold (backup.key.missing, with the key ids), an owner account that does not exist here, a newer schema, a copy too large to store (backup.upload.too_large). It was exempted from the contract as console plumbing while its two siblings were published for exactly that kind of refusal. It is registered with the three bodies it reads (raw JSON, raw gzip, a small multipart form) and every status it answers; the summary is the three identity fields plus one integer count per class, so a class the backup format gains later does not break a decoder. The round-trip test calls it through the contract now. --- docs/api/openapi.yaml | 142 ++++++++++++++++++ .../openapi-route-coverage-guard.test.ts | 12 +- src/lib/openapi/routes/admin-backups.ts | 112 +++++++++++++- tests/integration/settings-round-trip.test.ts | 3 - 4 files changed, 257 insertions(+), 12 deletions(-) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index e6762c399..a3bddb974 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -24785,6 +24785,93 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + /api/admin/backups/upload: + post: + tags: + - Admin + summary: Store an uploaded backup file as a new copy + description: "Reads a backup file, checks it, and stores it encrypted as a new copy beside the weekly one; it does NOT + restore anything. Restoring is the separate `POST /api/admin/backups/{id}/restore`, so an operator can review + what was uploaded first. The file goes up as the raw request body, plain JSON or gzip-compressed JSON (sniffed + from the bytes, not the content type), up to 512 MB as sent; or, for a file up to 10 MB, as + `multipart/form-data` with one `file` field. It is read as a stream, checked as it arrives, and stored in the + same pass; a file that fails any check leaves nothing behind. It is refused when it is not valid JSON, fails the + backup schema, was written by a newer schema version than this server reads, needs an encryption key this server + does not hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds` naming them), or belongs to an account + that does not exist here. Three uploads a minute per administrator. Admin session cookie required; Bearer tokens + cannot reach admin endpoints. A browser session needs a fresh proof as well as a live one: a sign-in or `POST + /api/auth/reproof` within the last five minutes (with a second factor, on an account that has one). Without it + the answer is 401 `auth.reproof.required`, with `meta.methods` naming the proofs the account can give." + requestBody: + required: true + content: + application/json: + schema: + type: object + propertyNames: + type: string + additionalProperties: {} + description: The backup document, as `GET /api/admin/backups/{id}/download` or the account export wrote it. + application/gzip: + schema: + type: string + format: binary + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: The backup file, plain or gzip-compressed, up to 10 MB. + required: + - file + responses: + "201": + description: The file was stored as a new copy. + content: + application/json: + schema: + $ref: "#/components/schemas/AdminBackupUploadEnvelope" + "400": + description: A `multipart/form-data` body that could not be parsed as a form. Nothing was stored. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "401": + description: "No session (`auth.missing`), or a session without a recent proof (`auth.reproof.required`, with + `meta.methods`). A Bearer token lands here too: the admin gate reads only the session cookie." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "403": *a25 + "413": + description: "Too large: over 512 MB as sent (10 MB in a form), a gzip file that expands past what any backup this + server wrote does, or larger than the stored-copy limit once encrypted (`meta.errorCode` = + `backup.upload.too_large`, the numbers and the setting in the message). Nothing was stored." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "422": + description: "The file was refused and nothing was stored: no file in the request, not valid JSON or not readable as + gzip, failed the backup schema (`meta.issues`), an unsupported schema version, a key this server does not + hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds`), or an owner account that does not exist + here." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "429": + description: More than three uploads in a minute by this administrator. Nothing was stored. The `Retry-After` and + `X-RateLimit-*` headers describe the bucket. + headers: *a8 + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" /api/admin/backups/{id}/download: get: tags: @@ -88681,6 +88768,61 @@ components: - data - error additionalProperties: false + AdminBackupUploadEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/AdminBackupUploadResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + AdminBackupUploadResponse: + type: object + properties: + id: + type: string + description: "`DataBackup.id` of the stored copy; pass it to `POST /api/admin/backups/{id}/restore`." + valid: + type: boolean + const: true + summary: + $ref: "#/components/schemas/AdminBackupUploadSummary" + required: + - id + - valid + - summary + additionalProperties: false + description: The file was read, checked and stored as a new copy. Nothing was restored. + AdminBackupUploadSummary: + type: object + properties: + schemaVersion: + type: string + userId: + type: string + exportedAt: + type: string + required: + - schemaVersion + - userId + - exportedAt + additionalProperties: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + description: "What the stored file holds: its `schemaVersion`, the account it was exported from (`userId`) and when + (`exportedAt`), and one count per serialised class (`measurements`, `medications`, `intakeEvents`, + `moodEntries`, …). The set of counts grows with the backup format; read the ones you know and show the rest + generically." AdminBackupDocument: type: object propertyNames: diff --git a/src/__tests__/openapi-route-coverage-guard.test.ts b/src/__tests__/openapi-route-coverage-guard.test.ts index a1210c215..cf7bf490b 100644 --- a/src/__tests__/openapi-route-coverage-guard.test.ts +++ b/src/__tests__/openapi-route-coverage-guard.test.ts @@ -216,11 +216,13 @@ const UNPUBLISHED: Readonly> = { "/api/admin/backup/test": { kind: "adminConsole", methods: ["POST"] }, "/api/admin/backups": { kind: "adminConsole", methods: ["GET"] }, "/api/admin/backups/run": { kind: "adminConsole", methods: ["POST"] }, - "/api/admin/backups/upload": { kind: "adminConsole", methods: ["POST"] }, - // `/api/admin/backups/{id}/download` and `/api/admin/backups/{id}/restore` - // are published rather than exempted: both open the encrypted stored copy, - // and the refusal they answer when it will not open is a promise an operator - // needs to be able to look up. See `src/lib/openapi/routes/admin-backups.ts`. + // `/api/admin/backups/{id}/download`, `/api/admin/backups/{id}/restore` and + // `/api/admin/backups/upload` are published rather than exempted: the first + // two open the encrypted stored copy, and the refusal they answer when it + // will not open is a promise an operator needs to be able to look up; the + // upload refuses a file for reasons (a missing key, an unknown owner, a + // newer schema) an operator has to be able to look up the same way. See + // `src/lib/openapi/routes/admin-backups.ts`. "/api/admin/backups/{id}/summary": { kind: "adminConsole", methods: ["GET"] }, "/api/admin/central-codex": { kind: "adminConsole", diff --git a/src/lib/openapi/routes/admin-backups.ts b/src/lib/openapi/routes/admin-backups.ts index 673eb6912..686c2adf5 100644 --- a/src/lib/openapi/routes/admin-backups.ts +++ b/src/lib/openapi/routes/admin-backups.ts @@ -1,13 +1,13 @@ /** * OpenAPI route table — the backups-console routes that read a stored copy, and - * the restore job they start. + * the restore job they start, and the upload that stores a copy to restore. * * Part of the OpenAPI route table; aggregated in `./index.ts`. * * Most of `/api/admin/backups/*` is deliberately unpublished: it is a * cookie-only console, `requireAdmin()` refuses every Bearer caller, and a - * contract nobody can call against is a contract nobody reads. These two are - * here for the opposite reason. Both open the encrypted stored copy (pieces in + * contract nobody can call against is a contract nobody reads. The two readers + * are here for the opposite reason. Both open the encrypted stored copy (pieces in * `data_backup_chunks`, or one value in `data_backups.data` for a copy written * before v1.39.2), and both therefore have a refusal an operator meets on * the day a key rotation went one step too far — the case @@ -16,6 +16,11 @@ * the handler happens to answer, and a promise belongs somewhere it can be * quoted from. * + * The upload is here for the same reason: it refuses a file the operator has + * in hand for reasons they have to be able to look up (a key this server does + * not hold, an owner that does not exist here, a newer schema), and its answer + * is the copy id the restore takes. + * * The restore answers 202 and runs as a background job, so the two routes that * report on that job are published with it: a 202 without its status route is * half a contract. @@ -23,7 +28,12 @@ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; -import { dataEnvelope, errorEnvelope, idempotencyKeyParameter } from "./shared"; +import { + dataEnvelope, + errorEnvelope, + idempotencyKeyParameter, + stdResponses, +} from "./shared"; const restoreRequest = z .object({ @@ -168,7 +178,101 @@ const notFoundResponse = { content: { "application/json": { schema: errorEnvelope } }, }; +const uploadSummary = z + .object({ + schemaVersion: z.string(), + userId: z.string(), + exportedAt: z.string(), + }) + .catchall(z.number().int()) + .meta({ + id: "AdminBackupUploadSummary", + description: + "What the stored file holds: its `schemaVersion`, the account it was exported from (`userId`) and when (`exportedAt`), and one count per serialised class (`measurements`, `medications`, `intakeEvents`, `moodEntries`, …). The set of counts grows with the backup format; read the ones you know and show the rest generically.", + }); + +const uploadStored = z + .object({ + id: z.string().meta({ + description: + "`DataBackup.id` of the stored copy; pass it to `POST /api/admin/backups/{id}/restore`.", + }), + valid: z.literal(true), + summary: uploadSummary, + }) + .meta({ + id: "AdminBackupUploadResponse", + description: + "The file was read, checked and stored as a new copy. Nothing was restored.", + }); + export const adminBackupPaths: NonNullable = { + "/api/admin/backups/upload": { + post: { + tags: ["Admin"], + summary: "Store an uploaded backup file as a new copy", + description: + "Reads a backup file, checks it, and stores it encrypted as a new copy beside the weekly one; it does NOT restore anything. Restoring is the separate `POST /api/admin/backups/{id}/restore`, so an operator can review what was uploaded first. The file goes up as the raw request body, plain JSON or gzip-compressed JSON (sniffed from the bytes, not the content type), up to 512 MB as sent; or, for a file up to 10 MB, as `multipart/form-data` with one `file` field. It is read as a stream, checked as it arrives, and stored in the same pass; a file that fails any check leaves nothing behind. It is refused when it is not valid JSON, fails the backup schema, was written by a newer schema version than this server reads, needs an encryption key this server does not hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds` naming them), or belongs to an account that does not exist here. Three uploads a minute per administrator. Admin session cookie required; Bearer tokens cannot reach admin endpoints. A browser session needs a fresh proof as well as a live one: a sign-in or `POST /api/auth/reproof` within the last five minutes (with a second factor, on an account that has one). Without it the answer is 401 `auth.reproof.required`, with `meta.methods` naming the proofs the account can give.", + requestBody: { + required: true, + content: { + "application/json": { + schema: z.record(z.string(), z.unknown()).meta({ + description: + "The backup document, as `GET /api/admin/backups/{id}/download` or the account export wrote it.", + }), + }, + "application/gzip": { + schema: z.string().meta({ format: "binary" }), + }, + "multipart/form-data": { + schema: z.object({ + file: z.string().meta({ + format: "binary", + description: + "The backup file, plain or gzip-compressed, up to 10 MB.", + }), + }), + }, + }, + }, + responses: { + "201": { + description: "The file was stored as a new copy.", + content: { + "application/json": { + schema: dataEnvelope(uploadStored, "AdminBackupUploadEnvelope"), + }, + }, + }, + "400": { + description: + "A `multipart/form-data` body that could not be parsed as a form. Nothing was stored.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "401": { + description: + "No session (`auth.missing`), or a session without a recent proof (`auth.reproof.required`, with `meta.methods`). A Bearer token lands here too: the admin gate reads only the session cookie.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "413": { + description: + "Too large: over 512 MB as sent (10 MB in a form), a gzip file that expands past what any backup this server wrote does, or larger than the stored-copy limit once encrypted (`meta.errorCode` = `backup.upload.too_large`, the numbers and the setting in the message). Nothing was stored.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "422": { + description: + "The file was refused and nothing was stored: no file in the request, not valid JSON or not readable as gzip, failed the backup schema (`meta.issues`), an unsupported schema version, a key this server does not hold (`meta.errorCode` = `backup.key.missing`, `meta.keyIds`), or an owner account that does not exist here.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "429": { + ...stdResponses["429"], + description: + "More than three uploads in a minute by this administrator. Nothing was stored. The `Retry-After` and `X-RateLimit-*` headers describe the bucket.", + }, + }, + }, + }, "/api/admin/backups/{id}/download": { get: { tags: ["Admin"], diff --git a/tests/integration/settings-round-trip.test.ts b/tests/integration/settings-round-trip.test.ts index d89a6a4e2..a04500096 100644 --- a/tests/integration/settings-round-trip.test.ts +++ b/tests/integration/settings-round-trip.test.ts @@ -1124,9 +1124,6 @@ describe("every setting a person can choose", () => { route: "/api/admin/backups/upload", body: exported.body, auth: operator, - contract: { - skip: "the operator's backup routes are not in the published contract", - }, }); expect( uploaded.status, From 7248347188596608bb2a33f76d5295957e91a42e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:18:56 +0200 Subject: [PATCH 09/14] test(integration): release the held lock when the migration has met it The 0412 lock-retry case let go of the lock on a 2.5 s timer. On a loaded runner the migrate CLI took longer than that to start, so the first attempt found the lock already free, applied cleanly and never produced the 55P03 the case is about. The lock is now released once the attempt is seen queued behind it in pg_stat_activity and gone again, which is the moment its own timeout fired, and the case fails outright if no attempt ever queued. It holds with the CLI start delayed by four seconds. --- .../migrate-deploy-lock-retry.test.ts | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/tests/integration/migrate-deploy-lock-retry.test.ts b/tests/integration/migrate-deploy-lock-retry.test.ts index b9678feaf..d4b0c6137 100644 --- a/tests/integration/migrate-deploy-lock-retry.test.ts +++ b/tests/integration/migrate-deploy-lock-retry.test.ts @@ -145,6 +145,47 @@ async function holdLock(): Promise { return holder; } +/** Sessions of this database queued behind a lock, other than the holder. */ +async function lockWaiters(db: Client, holderPid: number): Promise { + const { rows } = await db.query<{ n: string }>( + `SELECT count(*) AS n FROM pg_stat_activity + WHERE datname = current_database() + AND wait_event_type = 'Lock' + AND pid <> $1`, + [holderPid], + ); + return Number(rows[0]?.n ?? 0); +} + +/** + * Let go of the held lock once a migration attempt has queued behind it and + * given up. Resolves false when no attempt queued within the budget, so the + * test reports that instead of passing on a run that never met the lock. + */ +async function releaseAfterFirstTimeout(holder: Client): Promise { + const { rows } = await holder.query<{ pid: number }>( + "SELECT pg_backend_pid() AS pid", + ); + const holderPid = rows[0].pid; + const watcher = await connect(); + const until = async (predicate: (n: number) => boolean) => { + const deadline = Date.now() + 60_000; + while (Date.now() < deadline) { + if (predicate(await lockWaiters(watcher, holderPid))) return true; + await new Promise((resolve) => setTimeout(resolve, 25)); + } + return false; + }; + try { + const queued = await until((n) => n > 0); + if (queued) await until((n) => n === 0); + await holder.query("ROLLBACK"); + return queued; + } finally { + await watcher.end(); + } +} + async function state(db: Client) { const rows = await db.query( `SELECT migration_name, finished_at IS NOT NULL AS finished, @@ -181,15 +222,23 @@ describe("migration 0412 under a held lock", () => { add0412(dir, { marked: true }); const holder = await holdLock(); // The first attempt meets the lock; the holder lets go during the backoff. - const release = setTimeout(() => { - void holder.query("ROLLBACK"); - }, 2_500); + // The release follows the migration rather than a clock: it waits until + // the first attempt is seen queued behind the lock and then gone again + // (its one-second timeout fired), and lets go before the retry. A fixed + // delay raced the CLI's own start-up, which on a loaded runner outlasted + // the delay, so the first attempt found the lock already free. + const released = releaseAfterFirstTimeout(holder); const startedAt = Date.now(); const result = await migrateDeploy(dir); - clearTimeout(release); + const releasedInTime = await released; await holder.query("ROLLBACK").catch(() => {}); await holder.end(); + expect( + releasedInTime, + "the first attempt never queued behind the held lock", + ).toBe(true); + expect(result.code, result.stderr).toBe(0); expect(result.stderr).toContain("Database error code: 55P03"); expect(result.stdout).toContain( From b6e9eb98f74241c1e8b77a2f001b6c3e26549583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:18:57 +0200 Subject: [PATCH 10/14] test(integration): size the all-time count case to its measured cost The rollup parity case writes over half a million readings and folds them into DAY buckets: about 35 s on a CI runner alone. Run beside two other heavy files on the same Postgres it took 1.7 times as long and crossed the 60 s default. Nothing in the case waits on a lock: its advisory locks are scoped to the worker's own database, and the measurement with the database capped at two CPUs (14.6 s alone, 25.3 s beside the sharing matrix and the restore memory test) shows plain contention. The case gets a 150 s budget, twice the contended estimate. HEALTHLOG_INTEGRATION_PG_CPUS caps the database container so a CI runner's contention can be reproduced locally. --- docs/contributing/testing.md | 4 +++- .../dashboard-summary-reads.test.ts | 9 ++++++- tests/integration/global-setup.ts | 24 ++++++++++++------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index fddd0c974..d006b8b48 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -119,7 +119,9 @@ Each Vitest worker clones it on its first file (`CREATE DATABASE … TEMPLATE`, see `tests/integration/worker-database.ts`), so files run in parallel without seeing each other's rows, advisory locks or queue schema. The worker count follows the host (half the cores, at most eight) unless -`HEALTHLOG_INTEGRATION_WORKERS` pins it; CI uses three. +`HEALTHLOG_INTEGRATION_WORKERS` pins it; CI uses three. To reproduce a CI runner's +contention on a larger machine, cap the database container with +`HEALTHLOG_INTEGRATION_PG_CPUS=2`. `truncateAllTables` empties every table the migrations created and puts back the rows they seed (the mood-tag and symptom catalogues), so a test starts diff --git a/tests/integration/dashboard-summary-reads.test.ts b/tests/integration/dashboard-summary-reads.test.ts index 979c6746f..5df8ac0bd 100644 --- a/tests/integration/dashboard-summary-reads.test.ts +++ b/tests/integration/dashboard-summary-reads.test.ts @@ -335,5 +335,12 @@ describe("dashboard all-time counts — parity with the live count", () => { expect(legacy.get("PULSE")).toBeGreaterThan(500_000); expect(legacy.get("WEIGHT")).toBe(5); expect(legacy.get("BLOOD_GLUCOSE")).toBe(2); - }); + // Over half a million rows written and folded into DAY buckets: about + // 35 s on a CI runner when the file ran alone. With three files sharing + // the runner's Postgres it took 1.7 times as long (measured with the + // database capped at two CPUs, HEALTHLOG_INTEGRATION_PG_CPUS=2: 14.6 s + // alone, 25.3 s beside the sharing matrix and the restore memory test), + // past the suite's 60 s default. The work is the point of the case, so + // the budget is sized to it: twice the contended estimate. + }, 150_000); }); diff --git a/tests/integration/global-setup.ts b/tests/integration/global-setup.ts index 31fdc912b..da31f7124 100644 --- a/tests/integration/global-setup.ts +++ b/tests/integration/global-setup.ts @@ -44,7 +44,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = resolve(__dirname, "..", ".."); -let container: StartedPostgreSqlContainer | null = null; +let started: StartedPostgreSqlContainer | null = null; declare module "vitest" { export interface ProvidedContext { @@ -56,7 +56,7 @@ declare module "vitest" { export default async function setup( project: TestProject, ): Promise<() => Promise> { - container = await new PostgreSqlContainer("postgres:16-alpine") + let container = new PostgreSqlContainer("postgres:16-alpine") .withDatabase(TEMPLATE_DATABASE) .withUsername("healthlog") .withPassword("healthlog") @@ -74,10 +74,18 @@ export default async function setup( "full_page_writes=off", "-c", "max_connections=400", - ]) - .start(); + ]); + // A CPU quota for the database container, to reproduce a CI runner's + // contention on a larger machine: HEALTHLOG_INTEGRATION_PG_CPUS=2. + const cpus = Number.parseFloat( + process.env.HEALTHLOG_INTEGRATION_PG_CPUS ?? "", + ); + if (Number.isFinite(cpus) && cpus > 0) { + container = container.withResourcesQuota({ cpu: cpus }); + } + started = await container.start(); - const url = container.getConnectionUri() + "?schema=public&pgbouncer=false"; + const url = started.getConnectionUri() + "?schema=public&pgbouncer=false"; process.env.DATABASE_URL = url; // The explicit child environment keeps Prisma CLI on the same container. @@ -91,9 +99,9 @@ export default async function setup( project.provide("integrationDatabasePlan", await readTemplate(url)); return async () => { - if (container) { - await container.stop({ remove: true }); - container = null; + if (started) { + await started.stop({ remove: true }); + started = null; } }; } From eda90eeb560e142192520c6fa70a620fd2a46606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 07:31:15 +0200 Subject: [PATCH 11/14] fix(strava): read a dead refresh token from Strava's own fault shape Strava's token endpoint does not answer RFC 6749 errors. A refresh token it no longer honours, after the athlete revoked the app or the token was rotated away, is a 400 with the API's generic Fault: {"message":"Bad Request","errors":[{"resource":"RefreshToken", "field":"refresh_token","code":"invalid"}]}. The classifier looked for invalid_grant, which never arrives, so the failure was booked persistent and the connection never asked for a reconnect. The token error now carries the fault reduced to its resource, field and code (or an RFC error code, should one arrive), and the classifier lifts a 400 naming RefreshToken to reauth_required, from the error and from its message after a retry loses the prototype. Every other refusal is classified by its status as before, including the faults that name the Application. Strava documents the Fault and Error model but not these values; the body is the one several unrelated client libraries captured from live traffic between 2023 and 2026. --- .../strava/__tests__/token-refusal.test.ts | 140 ++++++++++++++++++ src/lib/strava/client.ts | 11 +- src/lib/strava/response-classifier.ts | 75 ++++++++-- src/lib/strava/sync.ts | 5 +- 4 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 src/lib/strava/__tests__/token-refusal.test.ts diff --git a/src/lib/strava/__tests__/token-refusal.test.ts b/src/lib/strava/__tests__/token-refusal.test.ts new file mode 100644 index 000000000..207f99f4a --- /dev/null +++ b/src/lib/strava/__tests__/token-refusal.test.ts @@ -0,0 +1,140 @@ +/** + * What Strava's token endpoint answers when it refuses, and what the sync + * makes of it. + * + * Strava does not answer RFC 6749 errors. A refused refresh is the API's + * generic `Fault`; the bodies below are the ones the endpoint sends, as + * captured from live traffic by several unrelated client libraries (Strava + * documents the `Fault` / `Error` model, not these particular values). The + * HTTP layer is the only thing replaced: `postToken`, the error it throws and + * the classifier all run as they do in production. + */ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@/lib/safe-fetch", () => ({ safeFetch: vi.fn() })); + +import { safeFetch } from "@/lib/safe-fetch"; + +import { exchangeCode, refreshAccessToken } from "../client"; +import { + classifyStravaError, + stravaFaultSummary, + StravaApiError, +} from "../response-classifier"; +import { classifyStravaFailure } from "../sync"; + +const creds = { clientId: "12345", clientSecret: "not-a-real-secret" }; + +const DEAD_REFRESH_TOKEN = { + message: "Bad Request", + errors: [ + { resource: "RefreshToken", field: "refresh_token", code: "invalid" }, + ], +}; +const BAD_CLIENT_ID = { + message: "Bad Request", + errors: [{ resource: "Application", field: "client_id", code: "invalid" }], +}; +const BAD_CLIENT = { + message: "Authorization Error", + errors: [{ resource: "Application", field: "", code: "invalid" }], +}; + +function answer(status: number, body: unknown) { + vi.mocked(safeFetch).mockResolvedValueOnce( + new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }), + ); +} + +async function refusal(run: () => Promise): Promise { + const err = await run().then( + () => null, + (e: unknown) => e, + ); + expect(err).toBeInstanceOf(StravaApiError); + return err as StravaApiError; +} + +beforeEach(() => { + vi.mocked(safeFetch).mockReset(); +}); + +describe("a refused Strava token request", () => { + it("asks for a reconnect when the refresh token is dead", async () => { + answer(400, DEAD_REFRESH_TOKEN); + const err = await refusal(() => refreshAccessToken("rt-dead", creds)); + + expect(err.httpStatus).toBe(400); + expect(err.upstreamError).toBe("RefreshToken.refresh_token:invalid"); + expect(classifyStravaError(err)).toBe("reauth_required"); + expect(classifyStravaFailure(err)).toBe("reauth_required"); + }); + + it("still asks for a reconnect when the error lost its prototype on a retry", async () => { + answer(400, DEAD_REFRESH_TOKEN); + const err = await refusal(() => refreshAccessToken("rt-dead", creds)); + const replayed = new Error(err.message); + + expect(replayed.message).toBe( + "Strava refreshAccessToken error: 400 - RefreshToken.refresh_token:invalid", + ); + expect(classifyStravaError(replayed)).toBe("reauth_required"); + }); + + it("leaves a fault that names the application to the status, as before", async () => { + // A client id or secret the instance holds wrongly. Whether Strava ever + // answers a per-person condition this way is not documented, so the + // status decides, as it always has: 400 persistent, 401 reconnect. + answer(400, BAD_CLIENT_ID); + const bad = await refusal(() => refreshAccessToken("rt-live", creds)); + expect(bad.upstreamError).toBe("Application.client_id:invalid"); + expect(classifyStravaError(bad)).toBe("persistent"); + + answer(401, BAD_CLIENT); + const unauthorised = await refusal(() => + refreshAccessToken("rt-live", creds), + ); + expect(unauthorised.upstreamError).toBe("Application:invalid"); + expect(classifyStravaError(unauthorised)).toBe("reauth_required"); + }); + + it("leaves a 400 that names neither as persistent and a 5xx as transient", async () => { + answer(400, { message: "Bad Request", errors: [] }); + const plain = await refusal(() => exchangeCode("code", creds)); + expect(plain.upstreamError).toBe("Bad Request"); + expect(classifyStravaError(plain)).toBe("persistent"); + + answer(503, { message: "Service Unavailable" }); + const outage = await refusal(() => refreshAccessToken("rt-live", creds)); + expect(classifyStravaError(outage)).toBe("transient"); + }); + + it("still honours a standards-shaped invalid_grant", async () => { + answer(400, { error: "invalid_grant" }); + const err = await refusal(() => refreshAccessToken("rt-dead", creds)); + expect(err.upstreamError).toBe("invalid_grant"); + expect(classifyStravaError(err)).toBe("reauth_required"); + }); +}); + +describe("stravaFaultSummary", () => { + it("names only the resource, field and code, and drops anything else", () => { + expect( + stravaFaultSummary({ + message: "Bad Request", + errors: [ + { + resource: "RefreshToken", + field: "refresh_token