chore: restore Sunrise v0.8.0 ancestry lost to the #39 squash — DO NOT SQUASH - #44
Merged
Conversation
… native port (#425) TypeScript 7.0 (GA 2026-07-08) is the native Go compiler port. Our code and tsconfig are already compatible (`tsc --noEmit` passes; we use none of the removed flags), but API-bound tooling isn't ready: - typescript-eslint 8 peer range is `typescript >=4.8.4 <6.1.0` and crashes at load under TS 7 (`Cannot read properties of undefined (reading 'Cjs')`) - Next.js's TypeScript detection doesn't recognise the native package Add a Dependabot ignore for `typescript >=7`, matching the existing ecosystem-readiness holds (eslint >=10, jsdom >=27, ...). Forks inherit this file as-is, so the hold applies downstream too. Security updates still flow for ignored ranges. Remove once typescript-eslint allows TS 7 and Next.js supports the native compiler package. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…dates Bumps the github-actions group with 2 updates in the / directory: [actions/setup-node](https://github.com/actions/setup-node) and [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog). Updates `actions/setup-node` from 6 to 7 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v6...v7) Updates `trufflesecurity/trufflehog` from 3.95.8 to 3.95.9 - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](trufflesecurity/trufflehog@v3.95.8...v3.95.9) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: trufflesecurity/trufflehog dependency-version: 3.95.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [hono](https://github.com/honojs/hono) from 4.12.26 to 4.12.32. - [Release notes](https://github.com/honojs/hono/releases) - [Commits](honojs/hono@v4.12.26...v4.12.32) --- updated-dependencies: - dependency-name: hono dependency-version: 4.12.32 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.4. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.1.2...v3.1.4) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.4 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
…ctory with 19 updates Bumps the development-minor-patch group with 18 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.5` | `3.3.6` | | [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.4` | `9.39.5` | | [@next/eslint-plugin-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-plugin-next) | `16.2.10` | `16.2.12` | | [@opentelemetry/context-async-hooks](https://github.com/open-telemetry/opentelemetry-js) | `2.9.0` | `2.10.0` | | [@opentelemetry/sdk-trace-base](https://github.com/open-telemetry/opentelemetry-js) | `2.9.0` | `2.10.0` | | [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui) | `6.6.6` | `6.9.1` | | [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.3.2` | `4.3.3` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.0` | `26.1.2` | | [autoprefixer](https://github.com/postcss/autoprefixer) | `10.5.2` | `10.5.4` | | [eslint](https://github.com/eslint/eslint) | `9.39.4` | `9.39.5` | | [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.2.10` | `16.2.12` | | [happy-dom](https://github.com/capricorn86/happy-dom) | `20.10.6` | `20.11.1` | | [lint-staged](https://github.com/lint-staged/lint-staged) | `17.0.8` | `17.2.0` | | [postcss](https://github.com/postcss/postcss) | `8.5.16` | `8.5.23` | | [prettier](https://github.com/prettier/prettier) | `3.9.4` | `3.9.6` | | [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) | `0.8.0` | `0.8.1` | | [tsx](https://github.com/privatenumber/tsx) | `4.23.0` | `4.23.1` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.63.0` | `8.65.0` | Updates `@eslint/eslintrc` from 3.3.5 to 3.3.6 - [Release notes](https://github.com/eslint/eslintrc/releases) - [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md) - [Commits](eslint/eslintrc@eslintrc-v3.3.5...eslintrc-v3.3.6) Updates `@eslint/js` from 9.39.4 to 9.39.5 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/commits/v9.39.5/packages/js) Updates `@next/eslint-plugin-next` from 16.2.10 to 16.2.12 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](https://github.com/vercel/next.js/commits/v16.2.12/packages/eslint-plugin-next) Updates `@opentelemetry/context-async-hooks` from 2.9.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](open-telemetry/opentelemetry-js@v2.9.0...v2.10.0) Updates `@opentelemetry/sdk-trace-base` from 2.9.0 to 2.10.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md) - [Commits](open-telemetry/opentelemetry-js@v2.9.0...v2.10.0) Updates `@react-email/ui` from 6.6.6 to 6.9.1 - [Release notes](https://github.com/resend/react-email/releases) - [Changelog](https://github.com/resend/react-email/blob/canary/packages/ui/CHANGELOG.md) - [Commits](https://github.com/resend/react-email/commits/@react-email/ui@6.9.1/packages/ui) Updates `@tailwindcss/postcss` from 4.3.2 to 4.3.3 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-postcss) Updates `@types/node` from 26.1.0 to 26.1.2 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `autoprefixer` from 10.5.2 to 10.5.4 - [Release notes](https://github.com/postcss/autoprefixer/releases) - [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md) - [Commits](postcss/autoprefixer@10.5.2...10.5.4) Updates `eslint` from 9.39.4 to 9.39.5 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v9.39.4...v9.39.5) Updates `eslint-config-next` from 16.2.10 to 16.2.12 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](https://github.com/vercel/next.js/commits/v16.2.12/packages/eslint-config-next) Updates `happy-dom` from 20.10.6 to 20.11.1 - [Release notes](https://github.com/capricorn86/happy-dom/releases) - [Commits](capricorn86/happy-dom@v20.10.6...v20.11.1) Updates `lint-staged` from 17.0.8 to 17.2.0 - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md) - [Commits](lint-staged/lint-staged@v17.0.8...v17.2.0) Updates `postcss` from 8.5.16 to 8.5.23 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](postcss/postcss@8.5.16...8.5.23) Updates `prettier` from 3.9.4 to 3.9.6 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](prettier/prettier@3.9.4...3.9.6) Updates `prettier-plugin-tailwindcss` from 0.8.0 to 0.8.1 - [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md) - [Commits](tailwindlabs/prettier-plugin-tailwindcss@v0.8.0...v0.8.1) Updates `tailwindcss` from 4.3.2 to 4.3.3 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/tailwindcss) Updates `tsx` from 4.23.0 to 4.23.1 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](privatenumber/tsx@v4.23.0...v4.23.1) Updates `typescript-eslint` from 8.63.0 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint) --- updated-dependencies: - dependency-name: "@eslint/eslintrc" dependency-version: 3.3.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@eslint/js" dependency-version: 9.39.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@next/eslint-plugin-next" dependency-version: 16.2.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@opentelemetry/context-async-hooks" dependency-version: 2.10.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch - dependency-name: "@opentelemetry/sdk-trace-base" dependency-version: 2.10.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch - dependency-name: "@react-email/ui" dependency-version: 6.9.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch - dependency-name: "@tailwindcss/postcss" dependency-version: 4.3.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@types/node" dependency-version: 26.1.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: autoprefixer dependency-version: 10.5.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: eslint dependency-version: 9.39.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: eslint-config-next dependency-version: 16.2.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: happy-dom dependency-version: 20.11.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch - dependency-name: lint-staged dependency-version: 17.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch - dependency-name: postcss dependency-version: 8.5.23 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: prettier dependency-version: 3.9.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: prettier-plugin-tailwindcss dependency-version: 0.8.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: tailwindcss dependency-version: 4.3.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: tsx dependency-version: 4.23.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: typescript-eslint dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com>
… with 39 updates Bumps the production-minor-patch group with 38 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) | `0.110.0` | `0.115.0` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1079.0` | `3.1095.0` | | [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) | `3.1079.0` | `3.1095.0` | | [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.4.0` | `5.5.7` | | [@prisma/adapter-pg](https://github.com/prisma/prisma/tree/HEAD/packages/adapter-pg) | `7.8.0` | `7.9.1` | | [@prisma/client](https://github.com/prisma/prisma/tree/HEAD/packages/client) | `7.8.0` | `7.9.1` | | [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.15` | `1.2.20` | | [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.18` | `1.1.23` | | [@radix-ui/react-avatar](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/avatar) | `1.2.1` | `1.2.6` | | [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.19` | `2.1.24` | | [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.11` | `2.1.15` | | [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.18` | `1.1.23` | | [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.2` | `2.3.7` | | [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.11` | `1.1.15` | | [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.2` | `1.4.7` | | [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.3.0` | `1.3.3` | | [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.2` | `1.3.7` | | [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.16` | `1.1.21` | | [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.11` | `1.2.16` | | [@react-email/render](https://github.com/resend/react-email/tree/HEAD/packages/render) | `2.0.10` | `2.1.0` | | [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) | `10.63.0` | `10.68.0` | | [@vercel/blob](https://github.com/vercel/storage/tree/HEAD/packages/blob) | `2.5.0` | `2.6.1` | | [better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth) | `1.6.23` | `1.6.25` | | [cron-parser](https://github.com/harrisiirak/cron-parser) | `5.6.1` | `5.6.2` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.4.11` | `3.4.12` | | [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js) | `1.13.8` | `1.13.9` | | [lru-cache](https://github.com/isaacs/node-lru-cache) | `11.5.1` | `11.5.2` | | [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.23.0` | `1.27.0` | | [next](https://github.com/vercel/next.js) | `16.2.10` | `16.2.12` | | [openai](https://github.com/openai/openai-node) | `6.45.0` | `6.49.0` | | [prisma](https://github.com/prisma/prisma/tree/HEAD/packages/cli) | `7.8.0` | `7.9.1` | | [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.7` | `19.2.8` | | [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.7` | `19.2.8` | | [react-easy-crop](https://github.com/ValentinH/react-easy-crop) | `6.1.0` | `6.2.3` | | [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.6.6` | `6.9.1` | | [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.81.0` | `7.83.0` | | [recharts](https://github.com/recharts/recharts) | `3.9.2` | `3.10.1` | | [resend](https://github.com/resend/resend-node) | `6.17.1` | `6.18.0` | Updates `@anthropic-ai/sdk` from 0.110.0 to 0.115.0 - [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases) - [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md) - [Commits](anthropics/anthropic-sdk-typescript@sdk-v0.110.0...sdk-v0.115.0) Updates `@aws-sdk/client-s3` from 3.1079.0 to 3.1095.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1095.0/clients/client-s3) Updates `@aws-sdk/s3-request-presigner` from 3.1079.0 to 3.1095.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1095.0/packages/s3-request-presigner) Updates `@hookform/resolvers` from 5.4.0 to 5.5.7 - [Release notes](https://github.com/react-hook-form/resolvers/releases) - [Commits](react-hook-form/resolvers@v5.4.0...v5.5.7) Updates `@prisma/adapter-pg` from 7.8.0 to 7.9.1 - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.9.1/packages/adapter-pg) Updates `@prisma/client` from 7.8.0 to 7.9.1 - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.9.1/packages/client) Updates `@radix-ui/react-accordion` from 1.2.15 to 1.2.20 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/accordion/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/accordion) Updates `@radix-ui/react-alert-dialog` from 1.1.18 to 1.1.23 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/alert-dialog/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/alert-dialog) Updates `@radix-ui/react-avatar` from 1.2.1 to 1.2.6 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/avatar) Updates `@radix-ui/react-dialog` from 1.1.18 to 1.1.23 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog) Updates `@radix-ui/react-dropdown-menu` from 2.1.19 to 2.1.24 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu) Updates `@radix-ui/react-label` from 2.1.11 to 2.1.15 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label) Updates `@radix-ui/react-popover` from 1.1.18 to 1.1.23 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover) Updates `@radix-ui/react-select` from 2.3.2 to 2.3.7 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select) Updates `@radix-ui/react-separator` from 1.1.11 to 1.1.15 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator) Updates `@radix-ui/react-slider` from 1.4.2 to 1.4.7 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slider/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slider) Updates `@radix-ui/react-slot` from 1.3.0 to 1.3.3 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot) Updates `@radix-ui/react-switch` from 1.3.2 to 1.3.7 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch) Updates `@radix-ui/react-tabs` from 1.1.16 to 1.1.21 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs) Updates `@radix-ui/react-tooltip` from 1.2.11 to 1.2.16 - [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md) - [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip) Updates `@react-email/render` from 2.0.10 to 2.1.0 - [Release notes](https://github.com/resend/react-email/releases) - [Changelog](https://github.com/resend/react-email/blob/canary/packages/render/CHANGELOG.md) - [Commits](https://github.com/resend/react-email/commits/@react-email/render@2.1.0/packages/render) Updates `@sentry/nextjs` from 10.63.0 to 10.68.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/10.68.0/CHANGELOG.md) - [Commits](getsentry/sentry-javascript@10.63.0...10.68.0) Updates `@vercel/blob` from 2.5.0 to 2.6.1 - [Release notes](https://github.com/vercel/storage/releases) - [Changelog](https://github.com/vercel/storage/blob/main/packages/blob/CHANGELOG.md) - [Commits](https://github.com/vercel/storage/commits/@vercel/blob@2.6.1/packages/blob) Updates `better-auth` from 1.6.23 to 1.6.25 - [Release notes](https://github.com/better-auth/better-auth/releases) - [Changelog](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/CHANGELOG.md) - [Commits](https://github.com/better-auth/better-auth/commits/v1.6.25/packages/better-auth) Updates `cron-parser` from 5.6.1 to 5.6.2 - [Release notes](https://github.com/harrisiirak/cron-parser/releases) - [Commits](harrisiirak/cron-parser@v5.6.1...v5.6.2) Updates `dompurify` from 3.4.11 to 3.4.12 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.11...3.4.12) Updates `libphonenumber-js` from 1.13.8 to 1.13.9 - [Changelog](https://gitlab.com/catamphetamine/libphonenumber-js/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/catamphetamine/libphonenumber-js/commits/master) Updates `lru-cache` from 11.5.1 to 11.5.2 - [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md) - [Commits](isaacs/node-lru-cache@v11.5.1...v11.5.2) Updates `lucide-react` from 1.23.0 to 1.27.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.27.0/packages/lucide-react) Updates `next` from 16.2.10 to 16.2.12 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](vercel/next.js@v16.2.10...v16.2.12) Updates `openai` from 6.45.0 to 6.49.0 - [Release notes](https://github.com/openai/openai-node/releases) - [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md) - [Commits](openai/openai-node@v6.45.0...v6.49.0) Updates `prisma` from 7.8.0 to 7.9.1 - [Release notes](https://github.com/prisma/prisma/releases) - [Commits](https://github.com/prisma/prisma/commits/7.9.1/packages/cli) Updates `react` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react) Updates `react-dom` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom) Updates `react-easy-crop` from 6.1.0 to 6.2.3 - [Release notes](https://github.com/ValentinH/react-easy-crop/releases) - [Commits](ValentinH/react-easy-crop@v6.1.0...v6.2.3) Updates `react-email` from 6.6.6 to 6.9.1 - [Release notes](https://github.com/resend/react-email/releases) - [Changelog](https://github.com/resend/react-email/blob/canary/packages/react-email/CHANGELOG.md) - [Commits](https://github.com/resend/react-email/commits/react-email@6.9.1/packages/react-email) Updates `react-hook-form` from 7.81.0 to 7.83.0 - [Release notes](https://github.com/react-hook-form/react-hook-form/releases) - [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md) - [Commits](react-hook-form/react-hook-form@v7.81.0...v7.83.0) Updates `recharts` from 3.9.2 to 3.10.1 - [Release notes](https://github.com/recharts/recharts/releases) - [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md) - [Commits](recharts/recharts@v3.9.2...v3.10.1) Updates `resend` from 6.17.1 to 6.18.0 - [Release notes](https://github.com/resend/resend-node/releases) - [Changelog](https://github.com/resend/resend-node/blob/canary/CHANGELOG.md) - [Commits](resend/resend-node@v6.17.1...v6.18.0) --- updated-dependencies: - dependency-name: "@anthropic-ai/sdk" dependency-version: 0.115.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1095.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@aws-sdk/s3-request-presigner" dependency-version: 3.1095.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@hookform/resolvers" dependency-version: 5.5.7 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@prisma/adapter-pg" dependency-version: 7.9.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@prisma/client" dependency-version: 7.9.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-accordion" dependency-version: 1.2.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-alert-dialog" dependency-version: 1.1.23 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-avatar" dependency-version: 1.2.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-dialog" dependency-version: 1.1.23 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-dropdown-menu" dependency-version: 2.1.24 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-label" dependency-version: 2.1.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-popover" dependency-version: 1.1.23 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-select" dependency-version: 2.3.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-separator" dependency-version: 1.1.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-slider" dependency-version: 1.4.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-slot" dependency-version: 1.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-switch" dependency-version: 1.3.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-tabs" dependency-version: 1.1.21 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@radix-ui/react-tooltip" dependency-version: 1.2.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@react-email/render" dependency-version: 2.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@sentry/nextjs" dependency-version: 10.68.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@vercel/blob" dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: better-auth dependency-version: 1.6.25 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: cron-parser dependency-version: 5.6.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: dompurify dependency-version: 3.4.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: libphonenumber-js dependency-version: 1.13.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: lru-cache dependency-version: 11.5.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: lucide-react dependency-version: 1.27.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: next dependency-version: 16.2.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: openai dependency-version: 6.49.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: prisma dependency-version: 7.9.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: react dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: react-dom dependency-version: 19.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: react-easy-crop dependency-version: 6.2.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: react-email dependency-version: 6.9.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: react-hook-form dependency-version: 7.83.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: recharts dependency-version: 3.10.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: resend dependency-version: 6.18.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com>
… is a one-line fork diff (#480) The test asserted each lib/app/* seam ships empty across nine hand-written blocks. That contract is worth keeping — a stray default registration would silently apply to every install — but every fork is expected to violate it, and the fix was an edit to a Sunrise-owned test file. That is the merge conflict the seam model exists to prevent, reintroduced at the test layer. Restructure as a single `it.each(SEAM_DEFAULTS)` table so filling a seam means pinning one row's expected value rather than rewriting a block, and document the pin-don't-delete guidance in a FORK NOTE and CUSTOMIZATION.md §4. Also closes the coverage gap the rewrite made visible: agent-fields, protected-routes, env, guard-floor-contributors and guard-event-contributors were all unasserted (9 seams covered -> 14). A new drift guard reads lib/app/ and fails if a seam file has no row, with db-drift.ts and surface.ts listed as explicit exceptions (covered elsewhere / ships real logic). CUSTOMIZATION.md §4's table was two rows stale (agent-fields, surface) and its stable-contract sentence enumerated exports that had drifted; the enumeration now points at the table instead of duplicating it. Refs #480 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NODE_OPTIONS hardcoded --max-old-space-size=5120, which is sized for base
Sunrise. A fork with a meaningful amount of added code pushes type-aware
ESLint past it and the lint job dies with exit 134 (SIGABRT / OOM) — no error
message, nothing pointing at memory, so it reads as a crashed toolchain.
The fix was a one-token edit to a platform CI file, so it reverted on every
upstream sync and the fork rediscovered the same opaque failure. Make it
`${{ vars.CI_NODE_HEAP_MB || 5120 }}` instead, mirroring the existing
CI_TEST_SCOPE knob, so a fork sets it once in repo settings and never touches
the workflow.
Documents the exit-134 symptom in .context/architecture/ci.md, since
attributing the failure is the expensive part; the doc's "one knob" framing
becomes "two knobs".
Closes #452
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing results (#453) TruffleHog runs with --results=verified,unknown. The `unknown` class matches by STRUCTURE, not validity, so a Postgres DSN in a test fixture or a docs example trips the Postgres detector, cannot be verified (it points at nothing real), and blocks the merge on a string that was never a secret. Fixture DSNs are unavoidable — integration tests and the database docs both need realistic connection strings. The workaround the existing comment suggested (drop to --results=verified) works but is a real reduction in coverage: it also discards genuinely unverifiable secrets, like a credential for a service TruffleHog has no verifier for or a revoked-but-still-committed key. A fork following that advice loses the class with no signal about what it gave up. Add .trufflehog-exclude.txt covering the paths where illustrative credentials are the point (tests/, .context/, .claude/, docs/, .env.example, compose files, scripts/spikes/) and keep both result classes. Real source stays scanned — deliberately not a blanket `\.md$`, since a pasted credential in a README is a genuine finding. The trade-off is now documented where the workaround used to be recommended. Closes #453 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…imeout (#454, #455)
Three fork-inherited harness gaps, all in the Vitest config surface.
next/font (#455): font loaders are compiled away by the Next build — the real
`next/font/google/index.js` is an EMPTY file — so under Vitest calling one
throws `X is not a function`. Loaders run at MODULE SCOPE, so the throw happens
at import time and takes down every test that imports the page or layout using
the font, including tests with nothing to do with fonts, with an error pointing
nowhere near the cause. Base Sunrise loads no custom font, but a root layout
with brand typography is one of the first things a fork adds.
The stub has to answer for ANY loader name (we cannot know which fonts a fork
picks) and must not live in tests/setup.ts, which is a platform file where every
fork edit is a merge conflict. Two simpler shapes were tried and measurably do
not work under Vitest 4: a `vi.mock` factory returning a Proxy (the factory
result is spread into a fixed namespace, so the Proxy collapses to `{}` and
every named import fails), and a CJS Proxy behind resolve.alias (Vite's interop
computes the export list from the target's own keys). ESM named imports need a
concrete export list, so it must be enumerated — but the list is DERIVED from
Next's own shipped declarations (1911 entries) rather than hardcoded, so it
tracks Next upgrades and covers whatever font a fork chooses.
tests/e2e (#454): forward-looking. Sunrise ships no Playwright suite, but a fork
that adds one uses the conventional `.spec.ts` suffix, which the include glob
collects — Vitest then runs files importing `@playwright/test` and the failures
don't obviously say "wrong runner". Costs nothing while the directory is absent.
testTimeout (#454): 10s -> 30s. Comfortable for the platform's own suite but
tight once a fork adds heavier component/integration tests; async server-
component renders and userEvent-driven form flows do 1-3s of real work and
inflate under CI contention. The failures are flaky rather than deterministic,
which makes them expensive to chase.
The new test is the only thing exercising the font stub (nothing in this repo
loads a font), so without it the stub could rot silently.
ESLint gains a narrow override letting root-level `*.config.*` files use
relative imports: vitest.config.ts DEFINES the @/ alias, and the tool bundles
the config before any alias exists, so a @/ specifier there cannot resolve. The
override is scoped to root config files and cannot leak into app or lib code.
Closes #454, closes #455
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No shared slug helper existed, so anything needing a filename- or URL-safe string wrote its own regex inline. Two deliberate choices, both tested: it returns the BARE slug including the empty string (an all-punctuation or all-CJK input legitimately slugifies to '', and the right fallback is caller-specific — 'report', a record id — so baking one in leads to surprising filenames); and it is pure and client-safe with no Node imports, so the same helper works in a download button and in a server-side PDF filename, which is the main reason to share it. The accent behaviour is asserted rather than left implicit: this is a filename-safety helper, not a transliterator, so an accented input loses the accented letters rather than being transliterated. Closes #451 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (#435)
lib/api/validation.ts shipped validators for request bodies and query strings
but not for dynamic route path segments, so every `[id]` route hand-rolled the
same helper — validate the segment as a CUID, throw ValidationError — under a
different name each time (parseCapabilityId, parseTagId, parseId, ...). Sixteen
copies of an identical seven-line function.
The new helper mirrors validateQueryParams exactly: same throw-on-invalid
contract, same ValidationError shape that handleAPIError already maps to a 400,
so it composes with everything downstream and no route's behaviour changes.
`label` preserves the entity-specific message ("Invalid capability id"), and
`field` covers differently-named segments.
One deliberate difference from the code it replaces: the error detail now comes
from the schema's own messages rather than a hardcoded 'Must be a valid CUID'
restated at each route, so the detail text has a single source of truth. No test
asserted that string for any migrated route.
Left alone on purpose: agents/[id]/capabilities/[capId] validates TWO segments
and accumulates both failures before throwing. Collapsing it to two sequential
calls would report only the first bad segment, which is worse for the caller —
noted in the helper's docstring.
Verified: 986 tests across 76 admin-orchestration route files pass unchanged.
Closes #435
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#452, #454, #455, #480) Per CLAUDE.md, only changes to the public surface get a bullet. slugify() and validatePathParam() are new public API; the seam-test restructure, the CI heap variable, the Vitest timeout and the next/font stub change what forks inherit, so they belong here too. #453's allowlist is recorded because a fork that had already dropped to --results=verified will want to revert that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…th globalThis (#462) Under Next 16 + Turbopack, `instrumentation.ts` runs in a SEPARATE module graph from route handlers and RSC. Both registries were plain module-scoped values, so each graph got its own copy: a tier registering only at boot saw an empty registry at request time. Silently — no error, the agent simply lacks the context block, and for capabilities `getCapabilityDefinitions()` filters out any slug the in-process dispatcher does not hold, so the tools vanish from the toolset rather than failing loudly at dispatch. Core already self-heals its own built-ins and the APP tier lazily on the request path; the asymmetry is that nothing covers a framework tier sitting between core and the app. Backing both with `globalThis` — the pattern `lib/db/client.ts` already uses for the Prisma client — gives every graph one instance. It also means registrations survive a dev hot-reload instead of resetting mid-session. The per-graph `appInited` latch stays module-scoped on purpose: it answers "have I run the fork's init in THIS bundle yet", and re-running an idempotent register-by-type init against the shared store is harmless. Refs #462 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (#457) `callMcpTool` dispatched without calling `registerBuiltInCapabilities()`. The chat path and the workflow path both warm the registry first; the MCP path did not. So on a server process that has only ever served MCP — no chat or workflow request yet — the in-memory registry is empty and EVERY tool call fails with `Unknown capability`, core built-ins included, while `tools/list` still returns the tool because it reads McpExposedTool rows from the DB. Discovery and dispatch disagree. Normal traffic masks this because some other request usually warms the registry first; a pure-MCP workload hits it on the first call. The warm-up must live on the dispatch path, not in instrumentation/initApp — those do not share a module graph with route handlers, so warming at boot leaves the route-realm registry empty. (#462, in this branch, makes the dispatcher itself graph-independent; this warms the *contents*, which is a separate step.) The test asserts ORDER, not just that both were called — warming after dispatch would fix nothing. Importing the real registry drags every built-in capability, and through run_workflow the whole engine-executor graph, into that unit test, so it is mocked as the collaborator it is. Closes #457 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#476)
Two defaults, each reasonable alone, combined into a privilege-escalation path.
The dispatcher synthesizes a default-ALLOW binding when no `AiAgentCapability`
pivot row exists ("use the capability defaults"), and the streaming handler
dispatched whatever tool name the model emitted without checking it against the
agent's advertised set. So a capability an agent was never granted would
execute — and execute UNRESTRICTED, because the row that would have carried a
`customConfig` scope pin is exactly the row that is missing.
That makes the intuitive way to withdraw a capability — delete the grant — the
action that WIDENS it: "pinned to one group" silently becomes "no restriction at
all". Two things make it reachable rather than theoretical: a prompt-injected
document can make the model emit any registered slug, and a conversation resumed
across a revocation carries the model's own earlier calls to the removed tool in
its history, which is the sort of thing a model imitates.
Fix, in two independent parts:
1. The chat handler now refuses any tool name not in `toolDefinitions` (the set
built from explicitly-enabled bindings and actually advertised to the model),
on both the single-call and parallel paths. Non-breaking — it only closes the
gap between what an agent advertises and what it can run. Crucially the
refusal still persists a tool-result row and pushes the assistant+tool
message pair: an unmatched toolCall makes the NEXT provider call 400.
2. `CAPABILITY_BINDING_MODE=strict` makes a missing row DENY instead. Opt-in and
default-unchanged, because flipping it retroactively revokes every capability
any agent relied on implicitly — including `mcp-system`, which dispatches
built-ins with no pivot rows in a default install.
Fixing a subtle bug found while wiring (2): the dispatch gate read
`if (binding && binding.isEnabled === false)`, so a null binding fell THROUGH to
a successful dispatch. Without changing that to `!binding`, strict mode would
have been a silent no-op. There is a regression test for exactly this.
`getAgentBinding`'s docstring now says plainly that deleting a grant does not
revoke a capability, and points at the supported alternative (keep the row, set
isEnabled false). Neither it nor `dispatch` said so, and both read as though
absence were the safe state.
The streaming-handler fixture advertised NOTHING while emitting tool calls,
which is also simply unrealistic — a real agent that calls search_knowledge_base
has it advertised. It now advertises what it dispatches, and four new tests
cover the refusal itself.
Refs #476
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The scheme check ran on `url.trim().toLowerCase()`, but `trim()` removes only
LEADING/TRAILING whitespace while the WHATWG URL parser (and every browser)
strips tab, newline and carriage return from ANYWHERE in a URL and drops leading
C0 controls BEFORE reading the scheme. So the guard and the browser disagreed,
and each of these was returned unchanged and then executed as `javascript:`:
java<TAB>script:alert(1) - tab inside the scheme
java<LF>script:alert(1) - newline inside the scheme
javascript<TAB>:alert(1) - tab before the colon
<0x01>javascript:alert(1) - leading C0 control (trim drops whitespace,
not 0x01-0x08 / 0x0e-0x1f)
All four are verified to have passed before this change and to be blocked after.
Only the INSPECTED copy is stripped; the original string is what gets returned,
so a legitimate URL containing a space is never rewritten - asserted, because
returning the normalised copy would be a quiet corruption bug.
This is the known sanitizer-bypass class that @braintree/sanitize-url and
DOMPurify strip for. Note the regex suggested in the issue does not compile
(reversed range); this strips U+0000-U+0020 plus U+007F, written as escapes
rather than literal control characters so the class stays visible in source.
Closes #437
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler accepted an `email` field from any authenticated user, checked only that no other account held that address, and spread the body straight into `prisma.user.update` — with no re-verification and without clearing `emailVerified`. An account that had verified mallory@example.com could become a *verified* ceo@bigco.example in one request. That turns `user.email` from "an address this person demonstrably controls" into "any unused string this person typed", while every downstream consumer still reads it as the former. Concretely: a fork resolving a pending invitation by matching `user.email` hands the invitation's tier to whoever claims the address first (privilege escalation for the attacker, denial of access for the intended recipient), any allowlist keyed on an email domain can be joined by typing the address, and audit trails attribute actions to an address the actor never proved they own. The attacker needs only an ordinary account. Now: changing the address clears `emailVerified` and re-triggers verification to the new address via better-auth, so the flag can only become true again the legitimate way. `email` stays in the schema — removing it would break existing clients, and clearing the flag is what actually closes the hole. Two details that matter in practice. The comparison is case-insensitive, so a form that PATCHes every field and re-submits the unchanged address does not unverify the account on an ordinary profile save. And the send is best-effort: the address is already changed and unverified by that point, so a mail failure must not fail the request — the user re-requests from the existing POST /api/auth/send-verification-email route. Deliberately NOT included: notifying the PREVIOUS address (the issue's third suggestion). It is good hygiene but needs a new email kind, and `EmailPropsMap` is a closed interface — that is #468, which opens it. Worth doing once it lands. Closes #466 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…66, #476) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pool was constructed with only a connection string, so node-postgres applied its default of 10 connections and no timeouts. On a function-per-request platform every warm instance holds its own pool — 20 instances is 200 connections against a Postgres that may allow far fewer, surfacing as intermittent `too many connections` errors that track traffic rather than any one query. Adds `DATABASE_POOL_MAX` (optional, positive int) and sets idle/connection timeouts to 10s. The default stays at 10: that is right for Sunrise's documented deploy target, one long-running process. Serverless operators set 1 behind a transaction pooler, and the Vercel and database env docs now say so. `connectionTimeoutMillis` matters independently of the cap — without it a request that cannot get a connection hangs until the platform kills it instead of failing with a usable error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e tracing (#446) pdfjs falls back to a "fake worker" loaded through `import(GlobalWorkerOptions.workerSrc)` — a variable specifier defaulting to a relative path. Vercel's Node file tracer can't follow it, so `pdf.worker.mjs` is never uploaded into the function and every PDF upload fails with "Setting up fake worker failed: Cannot find module …/pdf.worker.mjs". It works locally, so it only shows up after deploy. pdfjs checks `globalThis.pdfjsWorker?.WorkerMessageHandler` before attempting that import, so the parser now populates it from a literal specifier during its existing lazy load. The literal is traceable, which gets the worker file into the bundle, and pdfjs then uses it directly rather than resolving anything at runtime. Registration failure is non-fatal — pdfjs keeps its own fallback for environments where the relative path does resolve. `pdfjs-dist` stays undeclared (it's pdf-parse's dependency) on purpose: declaring it could resolve a second copy whose WorkerMessageHandler doesn't match the pdf.mjs doing the checking. The `serverExternalPackages` comment now covers both the bundling and the tracing half. Verified against the real library: the deep import resolves, the parse returns text, and no fake-worker line is emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (#444) `LlmOptions.timeoutMs` is documented as "override the provider's default request timeout", but neither adapter passed it on — every call got the client's construction-time default instead. A caller that needs several minutes (live document extraction on a reasoning model) set the option, watched the request die at the default, and had no reason to suspect a dropped parameter. `signal` was dropped the same way on the streaming paths, where nothing else was cancelling the in-flight request. Both SDKs take a second request-options argument, so `buildRequestOptions` in provider.ts translates the two fields and returns undefined when neither is set — leaving the client default in charge, so this is backward-compatible. Wired into all four call sites (chat and chatStream on each adapter). `structured-completion.ts` now sends its effective timeout alongside the abort signal it already built: the signal is absolute across retries, the timeout caps the individual request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`buildContext`, the per-user memory query, and `getCapabilityDefinitions` are independent, but ran one after another before the first token could be produced. Each pays a full app→Postgres round trip, and where the two aren't co-located — serverless especially — that dominates the delay the user sees before the response starts streaming. Batched into one `Promise.all`, the cost is the slowest read rather than the sum. `capabilityDefinitions` isn't consumed until the tool loop, and nothing between the two points affects it, so hoisting it into the batch changes no behaviour. Same queries, same results, same order of use. The regression guard gates all three mocks and asserts every one is in flight before any resolves — it fails against the serial version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… #446, #449) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (#461) `chatStreamEventSchema` listed every event type except this one, so `parseChatStreamEvent` returned null and consumers dropped the frame. On the tool-loop-abort path the handler yields it and returns immediately — `lib/api/sse.ts` closes cleanly on generator return, so no `done`/`error` follows. The reader sees the stream end normally, streaming flips false, and the assistant's turn is left empty or partial with no explanation that the per-turn cost cap fired. Adds the variant mirroring the canonical `ChatEvent` union, and gives `EvaluationRunner` — the other in-repo consumer of the typed helper — a terminal branch for it. `ChatInterface` dispatches off the raw frame and already handled it. The drift itself had no runtime signal, so the test file now carries a compile-time check that the two unions cover each other in both directions; type-check fails if either side gains a variant alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…indow (#456) `AiWorkflowExecution.totalCostUsd` is a scalar on the execution row, so it outlives the `AiCostLog` rows behind it. With `costLogRetentionDays < executionRetentionDays` the two prunes run independently and the drill-down for a still-retained execution empties out: a real total over an empty breakdown, with nothing to say whether that is a retention artefact or a bug in cost capture. Enforced at all three write paths, because the API allows what the UI does not: the settings form blocks the save client-side, the Zod schema rejects a whole-form save, and the PATCH route re-checks the patch against the persisted row so moving either side alone is caught. Installs already in this state never re-save settings, so `enforceRetentionPolicies()` logs a warning once per sweep when it sees the pair — one extra settings read per sweep, and a failed read is ignored rather than failing the sweep. The settings-form fixture was itself the incoherent pair (90 vs 180); corrected, which is a fair sign of how easy the mistake is to make. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(#436) "Spend by model" could label a model's spend with a provider the operator never configured — OpenAI's `gpt-4o` shown as `microsoft` / "GPT-4o (Azure)". The money was right; the provider, display name and tier were not, which makes the whole breakdown hard to trust. Three things combined: `getCostSummary` grouped cost logs by `model` alone, so a spend row carried no provider; the merged catalogue keeps one entry per `provider::modelId`, so both `gpt-4o` rows survive; and the display map was keyed on the bare id, last-write-wins — with DB-only rows appended last, the seeded Azure row won. `byModel` rows now carry `provider` from `AiCostLog`, and the costs surfaces resolve through a shared `provider::modelId` lookup with a bare-id fallback for providers outside the catalogue. The Provider column reads from the cost log rather than the catalogue, so it reports who actually billed. The trend chart can't be keyed this way — its rows come from `/costs?groupBy=model`, which has no provider — so its tier map is first-write-wins, which at least prefers the registry entry over an appended demo row. The Azure `gpt-4o` row is now seeded inactive, on create only so a re-seed can't deactivate a row an operator turned on. An unconfigured example provider shouldn't be competing for a shared model id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two module-scope constants added alongside the chat-event schema fix tripped `noUnusedLocals`, so type-check failed. Moved into a test body where they are read — the type annotations still carry the check, and it now shows up as a named test rather than an invisible declaration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`AiWorkflowSchedule.lastRunAt` is stamped by the scheduler tick and was already coming back from the GET route — the client's `Schedule` interface just didn't declare it, so the tab could say when a schedule would next run but not whether it ever had. That is the first thing an operator wants when a scheduled workflow appears to be doing nothing. Renders "never run yet" rather than a blank when the schedule hasn't fired, so the empty case reads as a fact rather than a loading state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… #461) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sunrise implemented the erasure half of GDPR carefully — `eraseUser()`, a documented per-table `onDelete` policy, an append-only receipt, and a registration seam for app-owned cleanup — and nothing at all for the access half. Every fork holding personal data had to write Art. 15 itself, and each one independently re-answered the same question: which tables count? `exportUserData()` is the counterpart. It assembles one subject's record from `SUBJECT_DATA_SOURCES`, a manifest where every `User`-linked model carries an explicit disposition — `export` for the subject's own data, `attribution` for org config they authored (id + label + date, not the config itself, since `createdBy` is attribution rather than ownership). A third list records what is deliberately left out, with reasons, and the export echoes all of it back in its own `meta` so a subject can see the boundary of what they received. The guard is the point. `export-sources.test.ts` parses `prisma/schema/*.prisma` and fails if a model relating to `User` is missing from the manifest, so adding a table without deciding what the subject receives breaks the build. Erasure gets this for free — a missing `onDelete` throws P2003 and breaks loudly. Access has no natural loud failure: an export that omits a table looks exactly like a complete answer to the person reading it, so the test is the only thing standing between a schema change and a quietly short answer. Two details that follow from the same reasoning: - Fetches use Prisma's `omit`, not `select`. Naming the secrets means a column added tomorrow is exported by default; an allowlist would silently narrow the export every time the schema grew. What gets omitted is credential material only — session tokens, password hashes, OAuth tokens, key hashes, HMAC secrets. - Nothing is best-effort. A source that throws fails the whole export, the opposite of the erasure path where hook failures are swallowed so app trouble can never block a deletion. The asymmetry follows from which failure the subject can detect. `ContactSubmission` is in the manifest by hand: the public contact form takes an address, not a session, so it has no `User` FK and is invisible to both the erasure cascade and the coverage scan. A test row pins it against tidy-up. The app seam is a static `collectAppSubjectData()` rather than a boot-time registry like `erasure-hooks.ts`. Erasure fails loudly if a hook never registers — the rows are still there. An unregistered export collector yields a bundle that looks complete and is not. Endpoints mirror the erasure pair: `GET /api/v1/users/me/export` (browser session only — a `chat`-scoped API key should not read out an entire account) and `GET /api/v1/users/[id]/export` for admins answering a request that arrives by email. Both take the `exportLimiter` sub-cap and send `Cache-Control: no-store`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… seam (#467)
The redaction cases assert the arguments that reach Prisma, not the rows that
come back. Asserting rows would only prove the mock returned what the mock was
told to return; asserting `omit: { token: true }` proves the manifest actually
withholds the credential — and these are the cases that would turn an access
response into a breach.
The service mock vends Prisma delegates on demand through a Proxy rather than
hand-declaring ~28 of them, so a source added to the manifest is covered
without editing the mock. `vi.clearAllMocks()` clears recorded calls but not
implementations, so the vended delegates are explicitly re-armed between tests —
otherwise a row count queued by one case leaks into the next and makes a later
assertion pass for the wrong reason.
Route coverage centres on the two refusals worth having: an API-key session
gets 403 and no export is built, and `SubjectNotFoundError` becomes a 404 while
any other service failure stays a 500 — collapsing those would tell an operator
a subject does not exist when the export merely broke.
`lib/app/data-export.ts` gets a row in the seam-defaults table, which caught its
absence on the first run exactly as designed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… gap (#467) `.context/privacy/data-export.md` is the app-author guide: the three dispositions and why `attribution` is not just `export`, why sources use `omit` rather than `select`, why the service fails whole where erasure degrades gracefully, and how a fork fills `collectAppSubjectData()` — including the part core cannot write for it, a guard over its own schema file. The erasure doc's "adding a new `User` relation" checklist gains a fourth step pointing at the manifest, since that is where someone adding a table actually looks, and its GDPR table moves Art. 15 and Art. 20 from ⏳ to ✅. The CLAUDE.md rule now covers both halves of the same decision — an `onDelete` policy and an export disposition — and says plainly not to delete a manifest row to make the guard pass, which is the one shortcut that would quietly undo the whole thing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (#467) The unit suite mocks Prisma, so it verifies the arguments the manifest builds and never that the resulting queries run. Type-checking covers column names; it does not cover `omit` combined with `include` on a relation load, or a case-insensitive filter against a real column. All ~28 sources had therefore never executed. `scripts/smoke/export.ts` runs them, against a throwaway subject carrying a session token, a password hash, an API-key hash and a webhook secret. The credential assertion is a recursive sweep over the whole serialised bundle rather than a per-table check, so a source added later without an `omit` fails here even if nobody writes a test for it. Two counter-assertions — the subject's own IP address and their message text ARE present — stop that sweep from passing on an empty export, which it otherwise would. Follows the erasure smoke's conventions: skips cleanly with no DB, creates only `smoke-test-export-*` rows, removes what it created on every path, and never exports a real user. Wired into CI directly after `smoke/erasure.ts`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r row (#467) Both gaps found by /pre-pr step 5c, not by the diff itself. `.context/api/user-endpoints.md` is the canonical reference for `/api/v1/users/*` and documented every handler except the two new ones. Added both, beside their erasure counterparts: the self-service route with its API-key refusal, and the admin route with the 404-vs-500 distinction. `.context/security/rate-limiting.md` described `exportLimiter` as "10/min per admin user | conversations export". The self-service export applies it to an ordinary user, so "per admin user" was now wrong — and wrong in the direction that matters, since a reader sizing the cap would have assumed only admins could reach it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by /security-review. Nothing in the export was insecure on its own — the bug is an assumption it inherited from a stale doc. `.context/privacy/data-erasure.md` stated that schedule- and inbound-triggered runs are system-owned (`userId = null`). They are not. The write paths stamp them with the operator who configured the trigger: app/api/v1/inbound/[channel]/[slug]/route.ts:284, :361, :408 trigger.createdBy lib/orchestration/scheduling/scheduler.ts:335 schedule.createdBy So an inbound SMS or email lands in a row labelled as belonging to the operator, while `fromAddress`, the message bodies and `inputData.trigger` (sender number, email From/Subject/body, base64 attachments) belong to whoever sent them. The export matched `userId` alone and would have handed one data subject another person's correspondence, presented as their own — a disclosure, and an Art. 15 answer that is wrong about whose data it is. Two sources now narrow: `channel: null` on conversations, `triggerSource: null` on executions. Both signals are precise — normal chat never sets `channel` (streaming-handler.ts), and `triggerSource` is written only by the inbound route (route.ts:260). Scheduled runs are deliberately not filtered: the scheduler leaves `triggerSource` unset and their input is the operator's own template, so they carry no third-party payload. Narrowing silently would have been the same failure this manifest exists to prevent, one level down — a count of 3 reads like a complete answer whether or not a fourth row was withheld. So `SubjectDataSource` gains `scopeNote`, required on both narrowed sources by a test, surfaced in `meta` beside the row count, and absent everywhere else so its presence means something. The smoke script now plants a third party's phone number and message on rows attributed to the subject and asserts neither reaches the bundle. Verified non-vacuous: with the filters removed it fails. This is containment, not the fix. The rows should carry `userId = null`, which also retires the more serious half of the same mis-attribution — the FK is `Cascade`, so erasing that one operator destroys every third party's inbound thread. The erasure doc now documents what the code actually does, with both consequences, so the next person does not inherit the assumption I did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both blocks say to delete themselves when the write paths are fixed, so they need the tracking link to be actionable rather than folklore. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ind it (#467) Found by /code-review. `FeatureFlag.createdBy` is a plain `String?` holding a user id — written by `POST /api/v1/admin/feature-flags` — with no Prisma relation behind it. The coverage guard matched on `x User? @relation(...)`, so it never saw the table, and a subject who created feature flags got no `attributions.featureFlags` section and no entry in `meta.excluded` explaining the absence. Exactly the silent omission the manifest exists to prevent. It is also the second instance of a class I documented and then failed to apply. The `ContactSubmission` comment already said "the guard cannot find them for you" — and there was one other table in that shape, unexamined. So this closes the class rather than the instance. The guard now casts two nets: the relation scan as before, plus a scan for `userId` / `createdBy` / `uploadedBy` / `ownerId` columns with no relation behind them. `FeatureFlag` is declared as an attribution source; `DataErasureReceipt` trips the second net and is allowlisted with its reason (the service fetches it directly into the bundle's `erasureReceipts`). Verified non-vacuous: removing the FeatureFlag source fails the new check by name. `ContactSubmission` remains reachable by neither net — it holds no user id in any column, only an email. That residual gap is now stated plainly in the code and the docs instead of being implied, because it is the reason the manifest still needs a human deciding what a new table holds. Also drops the "~28 sources" counts, which were about to go stale on every added source. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ery test green
CI shard 1/4 exited 1 while reporting 4853 passed and 0 failed. The exit code came
from an unhandled rejection, not a test:
TypeError: URL is not a constructor
at BrowserFrameNavigator.navigate (happy-dom)
at HTMLAnchorElement.click
at components/admin/orchestration/agents-table.tsx:394
`agents-table.test.tsx` stubbed the whole `URL` global with a plain object
carrying only `createObjectURL`/`revokeObjectURL`, so the constructor was gone.
The component's export path then calls `a.click()`, happy-dom treats that as a
navigation and does `new URL(href)`, and the TypeError is thrown inside
happy-dom's async event dispatch — which is why it surfaces as an unhandled
error that fails the run rather than as a failing assertion.
It also outlived the test. `vi.restoreAllMocks()` does not undo `stubGlobal`
(that needs `unstubAllGlobals`, and `unstubGlobals` is not set in
vitest.config.ts), so the broken global leaked into every later test in the file.
Spying on the two statics keeps the real constructor and is undone by the
existing `restoreAllMocks()` in afterEach. happy-dom provides both as real
functions, so there is nothing to shim.
Pre-existing, and unrelated to the export seam this branch adds — but it is a
race, and this branch's five new test files repartition the shards enough to lose
it. Fixed the same pattern in chat-interface.test.tsx, where it is currently
harmless only because `click` is separately stubbed; the rest of that file
already uses `Object.defineProperty`, which preserves the constructor.
Verified: all four shards now run clean (1029 files, 20,824 tests, no unhandled
errors).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ccess-export feat(privacy): add the subject-access export seam (#467)
Preparation for nulling `userId` on schedule- and inbound-triggered rows. Those columns are what every admin surface reads to decide visibility, so nulling them first would make each system-owned run and inbound thread disappear from the UI — unlistable, uncancellable, and, for a run paused at an approval gate, permanently stuck with nobody able to clear it. Adds `lib/orchestration/access/execution-access.ts` (basis `owner` | `system`) and a third `system` basis to the conversation equivalent, then routes all 15 execution routes, the counts badge, the live-engine snapshot, and the conversation list / detail / search through them. A null owner is the deployment's row, not a person's, so every admin may see it; one admin's own runs stay invisible to another, unchanged. Three judgement calls worth flagging: - Conversation PATCH/DELETE now accept `owner` and `system`, not `shared`. A share is still view-only consent. An inbound thread has no owner, so an owner-only rule would leave it undeletable — including when the person who sent the messages asks for them to go, the one erasure request that has no `eraseUser()` path because they have no account. - `system` access is audit-logged like `shared`, deletion included. The person on the far end has no account and cannot check the log themselves. - Bulk conversation CSV export stays own-rows-only, now stated in its header. Reading one inbound thread is audit-logged individually; handing over one file of hundreds of third parties' message bodies is a different act and needs its own decision. Rerun inherits the original's attribution rather than claiming the run for the admin who pressed the button — `inputData` is copied verbatim, so stamping a system-owned rerun with their id would re-create the very mis-attribution #502 exists to remove. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tor (#502) Closes the mis-attribution at its source. Schedule- and inbound-triggered rows are now written system-owned (`userId = null`), which is what `.context/privacy/data-erasure.md` always described and what the engine was already built for — `userId` is `string | null` throughout, and `user_memory` returns `no_user_context` rather than assuming a user. Five write sites moved: the inbound route's conversation, execution, audit row and engine context, and the scheduler's execution and engine context. The bug this retires was live and silent. `AiConversation.userId` and `AiWorkflowExecution.userId` are `onDelete: Cascade`, so erasing one operator destroyed every third party's inbound thread and run routed through any trigger they had configured — `eraseUser()` reported success and the correspondence was simply gone. The same rows matched that operator on `userId`, so a subject-access export would have handed them a stranger's phone number, email body and attachments labelled as their own data. Attribution is not lost. `AiWorkflowTrigger.createdBy` and `AiWorkflowSchedule.createdBy` still name the operator on the rows where authorship belongs, and `triggerSource` records what fired the run. The scheduler now writes `triggerSource: 'schedule'` — the value the schema comment has always documented and the scheduler never set — so a run with no owner still has provenance. The migration backfills history, which carried the identical cascade risk. Inbound rows are identifiable (`channel IS NOT NULL`, `triggerSource LIKE 'inbound:%'`); historical scheduled runs are not, because `triggerSource` was unset before this change and nulling every unmarked row would hide admins' own hand-started runs from them. They keep their author, they hold the operator's own inputTemplate rather than a third party's message, and the migration says so in full. One behaviour change worth calling out: `judge_call` refuses to run without a user context, and that now includes every scheduled and inbound run. It drives `streamChat`, which files the judge transcript — quoting the material under review — into a real account's chat history. Borrowing the schedule's author for that would re-create exactly the leak this commit removes, so the step errors instead, and its message now names the cause. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… gone (#502) `AiConversation` and `AiWorkflowExecution` filtered on `channel: null` and `triggerSource: null`, each with a `scopeNote` telling the subject their export had been narrowed. Both existed to contain a write path that stamped inbound rows with the operator who configured the channel; that path is fixed and the history backfilled, so the filters now match nothing and the notes would announce a narrowing that no longer happens — an export claiming to withhold data it holds in full is its own kind of dishonest. The guard tests are pinned in the opposite direction rather than deleted: these two sources must NOT narrow, and if a filter is ever reinstated it has to arrive with a `scopeNote`. The disclosure mechanism keeps its own test through a synthetic narrowed source, so the path stays covered while no shipped source uses it. Both smokes now assert the fix instead of the containment, against real Postgres: - `smoke:export` plants the third party's SMS thread and inbound run system-owned, as the route now writes them, and still sweeps the whole serialised bundle for their phone number and message. With no filter left to do the work, that proves attribution alone keeps them out. - `smoke:erasure` plants the same pair against the subject's own agent and workflow, then erases the subject and asserts all three rows SURVIVE. That is the half of #502 no mocked test can reach: the deletion it guards against was performed by Postgres, not by any line of application code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#502) Deletes the⚠️ block in `data-erasure.md#system-owned-runs` that #502 asked to remove, and replaces it with what the code now does — including the two rules a future change would otherwise break: admin surfaces gate on the system basis rather than an owner match, and a step needing a real account must refuse rather than borrow the schedule author's. The sweep is wider than the two privacy docs because the visibility change falsified claims spread across the substrate. Every place that said executions or conversations are "scoped to `session.user.id`" was true before this branch and misleading after it: - `orchestration-endpoints.md`, `admin-api.md` — ownership sections, the conversation consent paragraph, `DELETE /conversations/:id`, retry-step - `orchestration-conversations.md` — the three bases, and why export keeps only `'owner'` while list, detail and search take all three - `orchestration-executions-live-engine.md` — the four cards share `executionVisibilityWhere`; scheduled runs are what the dashboard is for - `provenance.md` — gated by `adminCanViewConversation`, not a userId match - `inbound-triggers.md`, `scheduling.md` — new attribution sections, and an anti-pattern entry, since "stamp the trigger's author on the row" reads like helpful provenance right up until an erasure runs `data-export.md` keeps the narrowing rule but records that nothing narrows today, and adds the lesson worth carrying: a row-level filter contains a disclosure and does nothing about the erasure half of the same mistake — check what the rows do when their apparent owner is erased. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…issed (#502) Three surfaces reach execution and conversation rows by routes the original pass didn't touch, so they kept comparing `userId` to the session id. Once inbound and scheduled rows started carrying `userId = null`, that comparison became unconditionally false: - `POST /workflows/:id/execute?resumeFromExecutionId=` — an admin could approve a paused system-owned run through `/executions/:id/approve` (correctly gated) and then never continue it. The run sat in `pending` until a maintenance sweep. This is the exact "a run paused at an approval gate could never be cleared" case the docs claim is fixed. - `GET /observability/dashboard-stats` — reported a healthy deployment while the live-engine dashboard, widened in the same PR, showed the same scheduled runs failing. - `POST /evaluations/datasets/:id/capture` — 404'd on every attempt to capture a scheduled run's output, a silent regression from behaviour that worked before the rows lost their owner. All three now route through `adminCanViewExecution` / `adminCanViewConversation`. Capture refuses a `'shared'` conversation: a share grants view consent, not consent to copy the turn into someone else's dataset, where it outlives the share and a revoke can't reach it. Dashboard filters are AND-composed so they can't flatten the visibility clause, and its conversation count uses the owner-or-system arm only — a conversation merely shared with an admin is still someone else's. Fixing the resume gate exposed a second problem it had been masking: the route passed `userId: session.user.id` into the engine. With resume working for system-owned runs, that would give a run's second half a user context its first half never had — `judge_call` filing a stranger's transcript into the resuming admin's history, `user_memory` reading their remembered facts from inbound traffic. The route now passes the execution row's `userId`, matching how resume already pins `versionId` and `scope`. For an owner-resume it is the same value. Also adds the `logConversationAccess` call missing from conversation PATCH. GET and DELETE in the same file log; PATCH did not, so a compliance query for "which conversations that weren't theirs did admin X touch?" silently missed every rename and archive — contradicting both the file's own header and the admin docs. It records `metadata.fields`, naming what changed rather than the values, so a renamed `title` doesn't put message content into the audit log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-owned-runs fix(privacy): stop attributing schedule- and inbound-triggered runs to an operator (#502)
…can't reach The existing playbook (.context/architecture/multi-tenancy.md) covers row isolation and covers it well: Postgres RLS below the query API, proven against real Postgres including the pooled-connection and empty-string-GUC footguns. Issues #366 and #367 cover the authorization axes. Between them it is easy to read the retrofit as scoped. It isn't. Row isolation is one of five isolation planes, and it is the only one anything currently addresses: 1. Row — table rows → RLS + orgId ✅ documented 2. Namespace — slugs, unique indexes, public URLs → ❌ 3. Process — in-memory caches, breakers → ❌ 4. Temporal — cron/reapers/retention, no request → ❌ 5. External — storage, credentials, logs → ❌ Three of the four uncovered planes are structurally unreachable from Postgres: a unique index is evaluated above the policy, a module-scoped Map lives in the Node heap, and S3 is not Postgres. Plane 4 is subtler — RLS depends on a per-transaction SET LOCAL, and background work has no request and therefore no org to set, so the choice is a BYPASSRLS ticker (which undoes the guarantee) or per-tenant iteration with fairness (which nothing currently supports). Above the planes sit the control plane (#366/#367) and the commercial plane — plans, quotas, metering, invoicing — for which no code exists at all. Adds .context/architecture/multi-tenancy-research.md: a gap register across all seven concerns, a platform-tier vs fork-tier ownership matrix (14 of 20 items are platform-tier; 2 are tracked), the 20-file merge-conflict surface a fork hits today, seam design principles distilled from Daybreak's canRead/ subjectScope work on #367, deployment-topology trade-offs, a risk register, and six evidence appendices verified against b7e30f0. Three drifts found while verifying and recorded in §12: - the playbook says 60 models; there are 61 - its raw-SQL table lists 6 files; there are 3 further app-layer sites - VERSIONING.md:75 names lib/tenancy/client.ts, which does not exist (the seam is lib/db/client.ts) The recommendation there is to enforce the raw-SQL and RLS-coverage inventories with tests in the shape of export-sources.test.ts, since a hand-maintained list of security-relevant sites demonstrably drifts. Documentation only — no runtime change, no public-surface change, so no CHANGELOG entry per the contract in VERSIONING.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ancy-research docs(architecture): multi-tenancy gap analysis — the four planes RLS can't reach
…ke when the app moved off localhost (#520)
* feat(dev): resolve the dev/preview server port from env files
Next's CLI binds `--port` to `PORT` at argument-parse time, before it
loads any `.env` file, so a `PORT=` line in `.env.local` was visible to
the app and invisible to the server hosting it. The only way to move a
dev server was `-p` on the command line — which, when several
Sunrise-derived apps run side by side behind proxied `*.test` hostnames,
means remembering which app owns which port every time.
`npm run dev`, `npm run start` and `npm run email:dev` now go through
`scripts/dev-server.mjs`. It reads only the port variable out of the env
files, in Next's own precedence order, and hands it to the child:
-p flag > real env var > .env.<NODE_ENV>.local > .env.local
> .env.<NODE_ENV> > .env > 3000
Every existing way of setting the port keeps working and keeps
outranking the files. Nothing else about env loading changes.
`EMAIL_PORT` does the same for the React Email preview server, which
also defaults to 3000; it has no env binding of its own, so the launcher
passes `-p`.
The launcher is plain .mjs with no runtime dependency — `npm start` must
survive `npm ci --omit=dev`, which prunes tsx and dotenv. Without dotenv
it still starts the server and says it could not read the files.
Deployed containers are untouched: the image runs the standalone server,
which reads `process.env.PORT` directly.
A port that is not 1-65535 fails fast, naming the file that set it,
rather than reaching a CLI that would reject it opaquely or — in
react-email's case — silently fall back to its own default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(dev): ship a committed .env.development pinning Sunrise to port 3010
The launcher added in the previous commit reads PORT from the env files;
this gives Sunrise itself one, so a fresh checkout binds 3010 with no
per-developer setup and leaves 3000 free for whatever else is running.
`.env.development` is the one env file .gitignore deliberately permits —
non-secret settings that should travel with the repo. Documented as such
in .gitignore, .env.example, and CUSTOMIZATION.md.
Forks inherit 3010 on their first merge and MUST change it: two
Sunrise-derived apps that both keep the default collide as soon as they
run together. CUSTOMIZATION.md § Claiming your own dev port covers the
change, the reverse-proxy setup, and the restart NEXT_PUBLIC_APP_URL
needs after an edit; it is also a checklist item in § First steps.
Deployment is unaffected, verified rather than assumed:
- Docker prod: the runtime stage copies only the standalone build, so
neither .env.development nor scripts/ reaches the image; ENV PORT=3000
stands.
- Docker dev: ENV PORT=3000 is a real environment variable, which
outranks any env file.
- Vercel: runs `next build`, never `npm start`.
- `npm start` anywhere: resolves against .env.production* / .env, since
Next only ever loads .env.<NODE_ENV> for the current mode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(dev): allow HMR from the hostname the app is served on
Next allows only localhost to reach its dev endpoints (the HMR socket
and /_next/* dev resources) and blocks everything else, so an app served
through a local reverse proxy rendered fine but never hot-reloaded:
⚠ Blocked cross-origin request to Next.js dev resource
/_next/webpack-hmr from "myapp.test".
Next's own advice is to hardcode the hostname in next.config.js, which
is wrong for a template — every fork would edit the same line, and the
value is already stated elsewhere. `allowedDevOrigins` is now derived
from the hostnames in NEXT_PUBLIC_APP_URL and BETTER_AUTH_URL, the URLs
the app is by definition served on. A fork that points those at its
proxied hostname gets hot reload with no config edit at all.
New optional ALLOWED_DEV_ORIGINS covers hosts those URLs do not: a LAN
IP for testing on a phone, or a `*.myapp.test` wildcard for
subdomain-per-tenant development. It is deliberately distinct from
ALLOWED_ORIGINS (API CORS, every environment) and both docs say so,
because the names are one letter apart in meaning.
Dev-only: Next ignores the key in production builds, so this widens
nothing in a deployed app.
eslint: `URL`/`URLSearchParams` added to the globals for plain JS files.
The config block listed only module/require/process/console/__dirname/
__filename, so no-undef fired on the WHATWG URL parser.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(api): call the app's own API at an address the server can reach
getBaseUrl() returned BETTER_AUTH_URL, so a server component rendering a
page called its own API by going out to the public hostname and back in.
Point that hostname at a local reverse proxy terminating TLS with a
certificate Node does not trust — Herd, Valet, mkcert — and every
self-call fails:
TypeError: fetch failed
cause: UNABLE_TO_VERIFY_LEAF_SIGNATURE
The browser is unaffected, because it trusts the same CA the server
does not. /admin/users reported "No users found" against a populated
database; the orchestration dashboard failed the same way on seven
loaders at once.
getBaseUrl() now resolves, in order:
1. INTERNAL_API_URL — explicit, wins everywhere
2. http://127.0.0.1:$PORT — development only, when the port is known
3. BETTER_AUTH_URL — unchanged, and correct whenever the public
URL is reachable from the server
Production is untouched unless INTERNAL_API_URL is set, which exists for
the same split elsewhere (a private network where the public hostname
resolves to something the app cannot call back through). Beyond
correctness, a self-call over loopback skips a needless round trip out
to the proxy.
Checked before changing it: proxy.ts validates Origin, not Host, and
only on state-changing methods — serverFetch sends no Origin header, so
the loopback Host does not trip CSRF. Nothing else in the self-call path
reads Host.
INTERNAL_API_URL is validated as a URL in lib/env.ts. It must be THIS
app's address; anything else would receive cookie-bearing internal calls,
and the docs say so.
The swallow-the-error-and-render-empty pattern that hid this is filed
separately as #518 — it is independent of the cause and will hide the
next one just as well.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(dev): cover the launcher's process wiring, not just its pure helpers
/pre-pr put scripts/dev-server.mjs at 35% lines / 41% statements — well
under the 80% threshold, and on the centrepiece of this branch. The
untested half was the part that actually runs: port resolution against
the environment, and main()'s dispatch, validation and spawn.
resolvePort() and main() now take their IO as injected dependencies
(environment, file reader, dotenv loader, spawn) rather than reaching for
the real thing, which is the same testability refactor used elsewhere in
this repo and avoids the v8-ignore dodge. Coverage: 89% lines, 82%
branches, 86% functions, 91% statements.
The injection also reaches a branch a dev checkout cannot reproduce: the
dotenv-pruned production install. That path was asserted in the commit
message and the docs but never executed; now it is.
Still uncovered and deliberately so: resolveBin/readProjectFile, thin
wrappers over existsSync/readFileSync, and the signal re-raise in the
exit handler, which would kill the test runner. Both were verified by
running the launcher against the real dev server. The test file's header
says which, rather than leaving a reader to wonder.
Also from /pre-pr step 5b — two docs still claimed Sunrise runs on 3000,
which stopped being true when .env.development pinned it to 3010:
README's quick start and first-admin signup link, and commands.md's dev
server and health-check lines.
Adds INTERNAL_API_URL validation tests to lib/env.test.ts: absent is
fine, a valid URL passes, and a bare host:port — the likely typo —
throws rather than silently sending every self-call somewhere
unresolvable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(api): separate the app's public URL from its internal one
getBaseUrl() had two callers with opposite needs, and the previous commit
only served one of them. Besides addressing the app's own API, it built
the inbound-webhook URL an operator copies into Slack or Postmark
(app/admin/orchestration/triggers/**, via TriggerForm's baseUrl). Once
the internal address became loopback in development, that screen would
have rendered http://127.0.0.1:3010/api/v1/inbound/... — a URL reachable
from nowhere but the developer's own machine, and no error to say so.
getPublicUrl() now returns the public address for anything that leaves
the server: webhook endpoints, links in email, URLs rendered for someone
to copy. getBaseUrl() is internal-only and its doc comment says not to
use it for those. The two trigger pages switch to getPublicUrl(),
restoring exactly the string they produced before this branch.
Also hardens the loopback branch: PORT is now checked for digits before
being interpolated. The launcher validates it, but the app is a separate
consumer of the same variable and `npm run dev -- -p 4100` deliberately
leaves PORT pointing somewhere else — falling through to the public URL
beats addressing a guess with the user's cookies attached.
Found by /security-review, which reported no HIGH or MEDIUM findings but
noted this while tracing the cookie-forwarding path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Bumps the github-actions group with 2 updates: [github/codeql-action](https://github.com/github/codeql-action) and [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog). Updates `github/codeql-action` from 4 to 4.37.4 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v4...v4.37.4) Updates `trufflesecurity/trufflehog` from 3.95.9 to 3.96.0 - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](trufflesecurity/trufflehog@v3.95.9...v3.96.0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.37.4 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: trufflesecurity/trufflehog dependency-version: 3.96.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the production-minor-patch group with 7 updates: | Package | From | To | | --- | --- | --- | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1095.0` | `3.1100.0` | | [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) | `3.1095.0` | `3.1100.0` | | [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas) | `1.0.2` | `1.0.3` | | [@sentry/nextjs](https://github.com/getsentry/sentry-javascript) | `10.68.0` | `10.69.0` | | [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js) | `1.13.9` | `1.13.10` | | [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.27.0` | `1.28.0` | | [resend](https://github.com/resend/resend-node) | `6.18.0` | `6.18.1` | Updates `@aws-sdk/client-s3` from 3.1095.0 to 3.1100.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1100.0/clients/client-s3) Updates `@aws-sdk/s3-request-presigner` from 3.1095.0 to 3.1100.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1100.0/packages/s3-request-presigner) Updates `@napi-rs/canvas` from 1.0.2 to 1.0.3 - [Release notes](https://github.com/Brooooooklyn/canvas/releases) - [Changelog](https://github.com/Brooooooklyn/canvas/blob/main/CHANGELOG.md) - [Commits](Brooooooklyn/canvas@v1.0.2...v1.0.3) Updates `@sentry/nextjs` from 10.68.0 to 10.69.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/10.69.0/CHANGELOG.md) - [Commits](getsentry/sentry-javascript@10.68.0...10.69.0) Updates `libphonenumber-js` from 1.13.9 to 1.13.10 - [Changelog](https://gitlab.com/catamphetamine/libphonenumber-js/blob/master/CHANGELOG.md) - [Commits](https://gitlab.com/catamphetamine/libphonenumber-js/commits/master) Updates `lucide-react` from 1.27.0 to 1.28.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.28.0/packages/lucide-react) Updates `resend` from 6.18.0 to 6.18.1 - [Release notes](https://github.com/resend/resend-node/releases) - [Changelog](https://github.com/resend/resend-node/blob/canary/CHANGELOG.md) - [Commits](resend/resend-node@v6.18.0...v6.18.1) --- updated-dependencies: - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1100.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@aws-sdk/s3-request-presigner" dependency-version: 3.1100.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@napi-rs/canvas" dependency-version: 1.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: "@sentry/nextjs" dependency-version: 10.69.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: libphonenumber-js dependency-version: 1.13.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: lucide-react dependency-version: 1.28.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: resend dependency-version: 6.18.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ctory with 4 updates (#524) Bumps the development-minor-patch group with 4 updates in the / directory: [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) and [lint-staged](https://github.com/lint-staged/lint-staged). Updates `@types/pg` from 8.20.0 to 8.20.3 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg) Updates `@types/react` from 19.2.17 to 19.2.18 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react-dom` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `lint-staged` from 17.2.0 to 17.3.0 - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md) - [Commits](lint-staged/lint-staged@v17.2.0...v17.3.0) --- updated-dependencies: - dependency-name: "@types/pg" dependency-version: 8.20.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@types/react" dependency-version: 19.2.18 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: "@types/react-dom" dependency-version: 19.2.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-minor-patch - dependency-name: lint-staged dependency-version: 17.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.1.4...v3.1.5) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.13 to 1.1.18. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](juliangruber/brace-expansion@v1.1.13...v1.1.18) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.18 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.6 to 4.2.7. - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.6...socket.io-parser@4.2.7) --- updated-dependencies: - dependency-name: socket.io-parser dependency-version: 4.2.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(release): Sunrise 0.8.0 Bump SUNRISE_VERSION, package.json and the lockfile's two version keys, and date the CHANGELOG's [Unreleased] batch as 0.8.0. MINOR per VERSIONING.md's 0.x rules: the batch adds substantial new public surface (the Art. 15 export seam, SIGNUP_MODE, the nav and auth-landing seams, private storage objects, several lib/app/* seams) and carries two breaking-in-0.x changes — the email-change flow on PATCH /api/v1/users/me, and HookEventType opening to fork-owned events. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(changelog): drop the PR-number ref from the 0.8.0 summary Every other reference in the summary blockquote points at an issue that the entry below it also cites. #520 was the pull request, and the dev-server-port entry it summarises carries no reference at all — so the citation pointed somewhere the rest of the section never points, for the one entry that can't corroborate it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit intentionally changes no files. It exists only to restore the ancestry link to Sunrise that was lost when #39 was squash-merged. #39 was a true merge of `v0.8.0`, but squashing it into 7e1155f kept the content and discarded the second parent. Git therefore no longer knew `v0.8.0` was an ancestor, and computed the merge base against upstream as `v0.7.0` — so the next `git merge v0.9.0` would have replayed the whole 0.7.0 -> 0.8.0 range again and re-conflicted all 342 files. `-s ours` is safe here precisely because the content is already correct: tree(7e1155f) is byte-identical (daa0d49) to tree(a217743), the tip of the branch that was merged. Nothing from v0.8.0 is missing, so recording it as merged asserts something already true. Effect: merge-base(main, upstream/main): v0.7.0 -> v0.8.0 files changed: 0 Verified on a scratch branch before pushing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
#39 was a genuine merge of
v0.8.0, but it was squash-merged. A squash keeps the content and throws away the second parent, so7e1155f5has one parent and git no longer knowsv0.8.0is an ancestor ofmain:The practical cost lands on the next Sunrise sync: with the merge base still at
v0.7.0,git merge v0.9.0would replay the entire 0.7.0 → 0.8.0 range a second time and re-conflict all 342 files — including the six already resolved by hand in #39.The fix
A
-s oursmerge that recordsv0.8.0as merged. It changes no files — the sole purpose is restoring the parent link.-s oursis normally dangerous (it discards the other side outright), but it's exactly right here because the content is already present and provably complete:Byte-identical. Nothing from
v0.8.0is missing, so declaring it merged asserts something already true rather than papering over a gap.Effect
v0.8.0ancestor ofmainupstream/mainv0.7.0v0.8.0Verified on a scratch branch before opening this: trees identical, ancestry restored, merge-base correct.
Why a PR rather than a direct push
The
Main protectionruleset requires status checks, so the repair commit needs a green run before it can land. There's no pull-request rule, but there's no way to satisfy required checks on a direct push either.Note on history shape
This leaves
mainwith the squash commit plus an empty-diff merge commit — slightly unusual to read, but functionally exact. The alternative (resettingmainand force-pushing the true merge) was rejected: the ruleset blocks non-fast-forward pushes, and it would have rewritten already-deployed history and orphaned #43's base.🤖 Generated with Claude Code