741fc07d405301c1e1477e97d3f74298d1a66862
281 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
741fc07d40 |
fix(deps): update dependency ioredis to v5.11.0 (#254)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ioredis](https://github.com/luin/ioredis) | dependencies | minor | [`5.10.1` -> `5.11.0`](https://renovatebot.com/diffs/npm/ioredis/5.10.1/5.11.0) | --- ### Release Notes <details> <summary>luin/ioredis (ioredis)</summary> ### [`v5.11.0`](https://github.com/luin/ioredis/blob/HEAD/CHANGELOG.md#5110-2026-05-26) [Compare Source](https://github.com/luin/ioredis/compare/v5.10.1...v5.11.0) ##### Bug Fixes - prevent RangeError from string accumulation in pipeline ([#​2088](https://github.com/luin/ioredis/issues/2088)) ([defc077](https://github.com/luin/ioredis/commit/defc07716a9ef10c2077ec4e23ea48cb9ea731fc)) - replace deprecated url.parse() with WHATWG URL API ([#​2081](https://github.com/luin/ioredis/issues/2081)) ([0021a45](https://github.com/luin/ioredis/commit/0021a4590e286aabbf27f4e2fc18f9d2de829ef0)), closes [redis/ioredis#1747](https://github.com/redis/ioredis/issues/1747) ##### Features - add array commands, typings and tests ([#​2114](https://github.com/luin/ioredis/issues/2114)) ([baf68d6](https://github.com/luin/ioredis/commit/baf68d6d89553672cfac3e08543467b910b561c5)) - add increx command ([#​2115](https://github.com/luin/ioredis/issues/2115)) ([37d0695](https://github.com/luin/ioredis/commit/37d0695b212d865ef24132acff85420ae51dde50)) - add Redis MSETEX support ([#​2111](https://github.com/luin/ioredis/issues/2111)) ([04a4615](https://github.com/luin/ioredis/commit/04a4615e8e96b9c58d017e360b5eaafede8973d0)) - add typed GCRA command support and functional tests ([#​2094](https://github.com/luin/ioredis/issues/2094)) ([468a802](https://github.com/luin/ioredis/commit/468a8023cd2c8f342ec7c55a01bf0c8d17e4b877)) - add vector set command support ([#​2116](https://github.com/luin/ioredis/issues/2116)) ([b7b3def](https://github.com/luin/ioredis/commit/b7b3defbd119d07fb86d071d5eefc255db4920c2)) - Add xnack command ([#​2103](https://github.com/luin/ioredis/issues/2103)) ([187d29b](https://github.com/luin/ioredis/commit/187d29b45000ee46a4baa8ce91eacfa04675aee8)) - Add zinter zunion count ([#​2104](https://github.com/luin/ioredis/issues/2104)) ([0d510bb](https://github.com/luin/ioredis/commit/0d510bbc1cfc8b01d862b76c408f6687f6e77809)) - Implement `TracingChannel` support ([#​2089](https://github.com/luin/ioredis/issues/2089)) ([4760e0a](https://github.com/luin/ioredis/commit/4760e0a19c194f29f4feb703003dcf046e4509cd)) #### [5.10.1](https://github.com/luin/ioredis/compare/v5.10.0...v5.10.1) (2026-03-19) ##### Bug Fixes - **cluster:** lazily start sharded subscribers ([#​2090](https://github.com/luin/ioredis/issues/2090)) ([4f167bb](https://github.com/luin/ioredis/commit/4f167bb9f494f0e8200a20dedd8bbdf1810fcd22)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/254 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
3ac408aca9 |
fix(deps): update dependency helmet to v8.2.0 (#253)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [helmet](https://helmet.js.org/) ([source](https://github.com/helmetjs/helmet)) | dependencies | minor | [`8.1.0` -> `8.2.0`](https://renovatebot.com/diffs/npm/helmet/8.1.0/8.2.0) | --- ### Release Notes <details> <summary>helmetjs/helmet (helmet)</summary> ### [`v8.2.0`](https://github.com/helmetjs/helmet/blob/HEAD/CHANGELOG.md#820---2026-05-21) [Compare Source](https://github.com/helmetjs/helmet/compare/v8.1.0...v8.2.0) - `Cross-Origin-Opener-Policy`: support `noopener-allow-popups`. See [#​522](https://github.com/helmetjs/helmet/pull/522) - Improve error message when passing duplicate options </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #253 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
377524ce3f |
chore(deps): update typescript tooling to v8.60.0 (#252)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@typescript-eslint/utils](https://typescript-eslint.io/packages/utils) ([source](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils)) | devDependencies | minor | [`8.59.4` -> `8.60.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2futils/8.59.4/8.60.0) | | [typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint) ([source](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)) | devDependencies | minor | [`8.59.4` -> `8.60.0`](https://renovatebot.com/diffs/npm/typescript-eslint/8.59.4/8.60.0) | --- ### Release Notes <details> <summary>typescript-eslint/typescript-eslint (@​typescript-eslint/utils)</summary> ### [`v8.60.0`](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/utils/CHANGELOG.md#8600-2026-05-25) [Compare Source](https://github.com/typescript-eslint/typescript-eslint/compare/v8.59.4...v8.60.0) This was a version bump only for utils to align it with other projects, there were no code changes. See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. </details> <details> <summary>typescript-eslint/typescript-eslint (typescript-eslint)</summary> ### [`v8.60.0`](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/typescript-eslint/CHANGELOG.md#8600-2026-05-25) [Compare Source](https://github.com/typescript-eslint/typescript-eslint/compare/v8.59.4...v8.60.0) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. See [GitHub Releases](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.60.0) for more information. You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website. </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #252 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
0df3ab9f03 |
chore(deps): update pnpm to v10.34.1 (#251)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm)) | packageManager | minor | [`10.33.4` -> `10.34.1`](https://renovatebot.com/diffs/npm/pnpm/10.33.4/10.34.1) | --- ### Release Notes <details> <summary>pnpm/pnpm (pnpm)</summary> ### [`v10.34.1`](https://github.com/pnpm/pnpm/releases/tag/v10.34.1): pnpm 10.34.1 [Compare Source](https://github.com/pnpm/pnpm/compare/v10.34.0...v10.34.1) #### Patch Changes - Reject `pnpm-lock.yaml` entries whose remote tarball `resolution:` block is missing the `integrity` field. Previously the worker that extracts a downloaded tarball skipped hash verification when no integrity was supplied and minted a fresh one from the unverified bytes, so an attacker who could both alter the lockfile (e.g. via a pull request that strips `integrity:`) and serve modified content at the referenced tarball URL could install a tampered package without any error — including under `--frozen-lockfile`. pnpm now fails closed at lockfile-read time with `ERR_PNPM_MISSING_TARBALL_INTEGRITY`. Git-hosted tarballs (`gitHosted: true` or a URL on codeload.github.com / bitbucket.org / gitlab.com) and `file:` tarballs are exempt — the commit SHA in a git-host URL and the user-controlled local path already anchor the bytes. <!-- sponsors --> #### Platinum Sponsors <table> <tbody> <tr> <td align="center" valign="middle"> <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a> </td> </tr> </tbody> </table> #### Gold Sponsors <table> <tbody> <tr> <td align="center" valign="middle"> <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" /> <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" /> <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a> </td> </tr> <tr> <td align="center" valign="middle"> <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" /> <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" /> <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" /> <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" /> </picture> </a> </td> </tr> <tr> <td align="center" valign="middle"> <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" /> <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" /> <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" /> </picture> </a> </td> </tr> </tbody> </table> <!-- sponsors end --> ### [`v10.34.0`](https://github.com/pnpm/pnpm/releases/tag/v10.34.0): pnpm 10.34 [Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.4...v10.34.0) #### Minor Changes - Treat tarball-integrity mismatches against the lockfile as a hard failure by default. Previously, `pnpm install` (non-frozen) would log `ERR_PNPM_TARBALL_INTEGRITY`, silently re-resolve from the registry, and overwrite the locked integrity — which meant a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed lockfile. `pnpm install` now exits with `ERR_PNPM_TARBALL_INTEGRITY` and a hint pointing at the new opt-in flag. The only opt-in is **`pnpm install --update-checksums`** — narrowly scoped to refreshing the locked integrity values from what the registry currently serves. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the operation is auditable. `--force` and `pnpm update` deliberately do **not** bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. `--frozen-lockfile` behavior is unchanged. `--fix-lockfile` keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass. #### Patch Changes - Pin unscoped per-registry settings (`_authToken`, `_auth`, `username`/`_password`, `tokenHelper`, inline `cert`/`key`) to the registry declared in the same config source at load time, so a later layer overriding `registry=` (workspace `.npmrc`, `pnpm-workspace.yaml`, CLI `--registry`) cannot redirect a credential or client certificate authored for a different host. A deprecation warning is emitted whenever an unscoped per-registry setting is encountered, naming the source and the URL it was pinned to. Reported by JUNYI LIU. - Fixed `minimumReleaseAge` handling when cached metadata is abbreviated. The npm registry returns abbreviated package metadata (without the per-version `time` field) by default, which made the maturity check throw `ERR_PNPM_MISSING_TIME` whenever cached abbreviated metadata was reused. pnpm now upgrades cached abbreviated metadata to the full document via a follow-up fetch when `minimumReleaseAge` is active, persists the upgrade to the on-disk cache so subsequent installs skip the extra fetch, and lets `ERR_PNPM_MISSING_TIME` from the cache fast-path fall through to the network fetch even under strict mode. - Reject git resolutions whose `commit` field is not a 40-character hexadecimal SHA before invoking `git`. A malicious lockfile could otherwise smuggle a value such as `--upload-pack=<command>` through `git fetch` / `git checkout`, which on SSH or local-file transports executes the supplied command. - Reject patch files whose `diff --git` headers reference paths outside the patched package directory. Previously a malicious `.patch` file added via a pull request could write, delete, or rename arbitrary files reachable by the user running `pnpm install`. - Fixed `--prefix=<dir>` not being honored when locating the workspace root. The `--prefix → dir` rename was applied after workspace detection, so workspace settings declared in `<dir>/pnpm-workspace.yaml` were not loaded when pnpm was invoked from outside `<dir>` [#​11535](https://github.com/pnpm/pnpm/issues/11535). - Reject dependency aliases that contain path-traversal segments (such as `@x/../../../../../.git/hooks`) when reading them from a package manifest or symlinking them into `node_modules`. A malicious registry package could otherwise use a transitive dependency key to make `pnpm install` create symlinks at attacker-chosen paths outside the intended `node_modules` directory. <!-- sponsors --> #### Platinum Sponsors <table> <tbody> <tr> <td align="center" valign="middle"> <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a> </td> </tr> </tbody> </table> #### Gold Sponsors <table> <tbody> <tr> <td align="center" valign="middle"> <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" /> <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" /> <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a> </td> </tr> <tr> <td align="center" valign="middle"> <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" /> <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" /> <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" /> <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" /> </picture> </a> </td> </tr> <tr> <td align="center" valign="middle"> <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" /> <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" /> </picture> </a> </td> <td align="center" valign="middle"> <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"> <picture> <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" /> <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" /> <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" /> </picture> </a> </td> </tr> </tbody> </table> <!-- sponsors end --> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/251 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
afbad752fb |
chore(deps): update dependency @oxc-project/runtime to ^0.133.0 (#250)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@oxc-project/runtime](https://oxc.rs) ([source](https://github.com/oxc-project/oxc/tree/HEAD/npm/runtime)) | devDependencies | minor | [`^0.131.0` -> `^0.133.0`](https://renovatebot.com/diffs/npm/@oxc-project%2fruntime/0.131.0/0.133.0) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #250 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
33d2257cf5 |
fix(deps): update vitest to v4.1.7 (#249)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@vitest/coverage-v8](https://vitest.dev/guide/coverage) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)) | devDependencies | patch | [`4.1.6` -> `4.1.7`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.6/4.1.7) | | [@vitest/ui](https://vitest.dev/guide/ui) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui)) | devDependencies | patch | [`4.1.6` -> `4.1.7`](https://renovatebot.com/diffs/npm/@vitest%2fui/4.1.6/4.1.7) | | [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | devDependencies | patch | [`4.1.6` -> `4.1.7`](https://renovatebot.com/diffs/npm/vitest/4.1.6/4.1.7) | | [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | dependencies | patch | [`4.1.6` -> `4.1.7`](https://renovatebot.com/diffs/npm/vitest/4.1.6/4.1.7) | --- ### Release Notes <details> <summary>vitest-dev/vitest (@​vitest/coverage-v8)</summary> ### [`v4.1.7`](https://github.com/vitest-dev/vitest/releases/tag/v4.1.7) [Compare Source](https://github.com/vitest-dev/vitest/compare/v4.1.6...v4.1.7) ##### 🐞 Bug Fixes - **runner**: Limit concurrency per task branch in addition to per leaf callbacks (backport) - by [@​hi-ogawa](https://github.com/hi-ogawa) in https://github.com/vitest-dev/vitest/issues/10384 [<samp>(4f0f2)</samp>](https://github.com/vitest-dev/vitest/commit/4f0f2a1ee) ##### [View changes on GitHub](https://github.com/vitest-dev/vitest/compare/v4.1.6...v4.1.7) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/249 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
17bed9de09 |
fix(deps): update nx to v22.7.5 (#248)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@nx/angular](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/angular)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fangular/22.7.2/22.7.5) | | [@nx/devkit](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/devkit)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fdevkit/22.7.2/22.7.5) | | [@nx/eslint](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/eslint)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2feslint/22.7.2/22.7.5) | | [@nx/eslint-plugin](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/eslint-plugin)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2feslint-plugin/22.7.2/22.7.5) | | [@nx/jest](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/jest)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fjest/22.7.2/22.7.5) | | [@nx/js](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/js)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fjs/22.7.2/22.7.5) | | [@nx/nest](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/nest)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fnest/22.7.2/22.7.5) | | [@nx/node](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/node)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fnode/22.7.2/22.7.5) | | [@nx/playwright](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/playwright)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fplaywright/22.7.2/22.7.5) | | [@nx/vite](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/vite)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fvite/22.7.2/22.7.5) | | [@nx/vite](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/vite)) | dependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fvite/22.7.2/22.7.5) | | [@nx/vitest](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/vitest)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fvitest/22.7.2/22.7.5) | | [@nx/web](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/web)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fweb/22.7.2/22.7.5) | | [@nx/webpack](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/webpack)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/@nx%2fwebpack/22.7.2/22.7.5) | | [nx](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/nx)) | devDependencies | patch | [`22.7.2` -> `22.7.5`](https://renovatebot.com/diffs/npm/nx/22.7.2/22.7.5) | --- ### Release Notes <details> <summary>nrwl/nx (@​nx/angular)</summary> ### [`v22.7.5`](https://github.com/nrwl/nx/releases/tag/22.7.5) [Compare Source](https://github.com/nrwl/nx/compare/22.7.4...22.7.5) ##### 22.7.5 (2026-05-27) ##### 🩹 Fixes - **core:** update tmp to 0.2.6 due to CVE-2026-44705 ([#​35813](https://github.com/nrwl/nx/pull/35813)) ##### ❤️ Thank You - Jack Hsu [@​jaysoo](https://github.com/jaysoo) ### [`v22.7.4`](https://github.com/nrwl/nx/releases/tag/22.7.4) [Compare Source](https://github.com/nrwl/nx/compare/22.7.3...22.7.4) ##### 22.7.4 (2026-05-25) ##### 🩹 Fixes - **core:** update brace-expansion and yaml ([#​35790](https://github.com/nrwl/nx/pull/35790)) ##### ❤️ Thank You - Jack Hsu [@​jaysoo](https://github.com/jaysoo) ### [`v22.7.3`](https://github.com/nrwl/nx/releases/tag/22.7.3) [Compare Source](https://github.com/nrwl/nx/compare/22.7.2...22.7.3) ##### 22.7.3 (2026-05-22) ##### 🚀 Features - **js:** support pnpm 11.2.2 ([#​35772](https://github.com/nrwl/nx/pull/35772)) ##### 🩹 Fixes - **angular:** only add [@​oxc-project/runtime](https://github.com/oxc-project/runtime) on the vitest-analog path ([#​35734](https://github.com/nrwl/nx/pull/35734)) - **angular-rspack:** exclude eslint config from tailwind v4 source scan ([#​35663](https://github.com/nrwl/nx/pull/35663)) - **core:** warn before installing unknown npm packages as preset ([#​35644](https://github.com/nrwl/nx/pull/35644)) - **core:** preserve input order in createNodes plugin results ([#​35595](https://github.com/nrwl/nx/pull/35595)) - **core:** resolve local plugin subpath imports from source ([#​35631](https://github.com/nrwl/nx/pull/35631)) - **core:** treat undefined task parallelism as parallel when scheduling ([#​35736](https://github.com/nrwl/nx/pull/35736)) - **core:** handle object form of bin field in getPrettierPath ([#​35680](https://github.com/nrwl/nx/pull/35680)) - **core:** detect vscode copilot ai agent ([#​35757](https://github.com/nrwl/nx/pull/35757)) - **core:** allow local plugin subpath imports without custom conditions ([#​35751](https://github.com/nrwl/nx/pull/35751), [#​35631](https://github.com/nrwl/nx/issues/35631)) - **dotnet:** include Directory.*.* files in inputs ([#​35738](https://github.com/nrwl/nx/pull/35738)) - **gradle:** add transitive:true to all tasks ([#​35677](https://github.com/nrwl/nx/pull/35677)) - **gradle:** pin generated e2e project toolchain to installed JDK ([#​35703](https://github.com/nrwl/nx/pull/35703)) - **js:** fall back to npm publish when bun publish fails with auth error ([#​35756](https://github.com/nrwl/nx/pull/35756)) - **linter:** improve convert-to-flat-config output fidelity ([#​35330](https://github.com/nrwl/nx/pull/35330)) - **linter:** only rewrite workspace-package peer deps to workspace:\* ([#​35423](https://github.com/nrwl/nx/pull/35423), [#​35318](https://github.com/nrwl/nx/issues/35318), [#​33417](https://github.com/nrwl/nx/issues/33417)) - **misc:** stop inferring `projects: 'self'` in `dependsOn` entries ([#​35686](https://github.com/nrwl/nx/pull/35686)) - **misc:** skip `$` escaping in file paths on windows ([#​35692](https://github.com/nrwl/nx/pull/35692)) - **repo:** run dotnet restore before publish ([#​35771](https://github.com/nrwl/nx/pull/35771)) - **repo:** run dotnet restore before macos e2e job ([#​35774](https://github.com/nrwl/nx/pull/35774)) - **rsbuild:** infer build outputs from distPath.root directly ([#​35707](https://github.com/nrwl/nx/pull/35707)) - **rsbuild:** lazy-require [@​rsbuild/core](https://github.com/rsbuild/core) in plugin so spec mocks work after jest.resetModules ([#​35707](https://github.com/nrwl/nx/issues/35707)) - **testing:** correct yargs-parser import in getJestProjectsAsync ([#​35672](https://github.com/nrwl/nx/pull/35672), [#​35654](https://github.com/nrwl/nx/issues/35654)) ##### ❤️ Thank You - AgentEnder [@​AgentEnder](https://github.com/AgentEnder) - Artur [@​arturovt](https://github.com/arturovt) - Benjamin Staneck [@​Stanzilla](https://github.com/Stanzilla) - Copilot [@​Copilot](https://github.com/Copilot) - Craigory Coppola [@​AgentEnder](https://github.com/AgentEnder) - FrozenPandaz [@​FrozenPandaz](https://github.com/FrozenPandaz) - Jason Jean [@​FrozenPandaz](https://github.com/FrozenPandaz) - Leosvel Pérez Espinosa [@​leosvelperez](https://github.com/leosvelperez) - leosvelperez [@​leosvelperez](https://github.com/leosvelperez) - Louie Weng [@​lourw](https://github.com/lourw) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/248 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
ffed212cf9 |
fix(deps): update angular to v21.2.15 (#247)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular/common](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/common)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fcommon/21.2.14/21.2.15) | | [@angular/compiler](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/compiler)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fcompiler/21.2.14/21.2.15) | | [@angular/compiler-cli](https://github.com/angular/angular/tree/main/packages/compiler-cli) ([source](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli)) | devDependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fcompiler-cli/21.2.14/21.2.15) | | [@angular/core](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/core)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fcore/21.2.14/21.2.15) | | [@angular/forms](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/forms)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fforms/21.2.14/21.2.15) | | [@angular/language-service](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/language-service)) | devDependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2flanguage-service/21.2.14/21.2.15) | | [@angular/localize](https://github.com/angular/angular) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2flocalize/21.2.14/21.2.15) | | [@angular/platform-browser](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/platform-browser)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser/21.2.14/21.2.15) | | [@angular/router](https://github.com/angular/angular/tree/main/packages/router) ([source](https://github.com/angular/angular/tree/HEAD/packages/router)) | dependencies | patch | [`21.2.14` -> `21.2.15`](https://renovatebot.com/diffs/npm/@angular%2frouter/21.2.14/21.2.15) | --- ### Release Notes <details> <summary>angular/angular (@​angular/common)</summary> ### [`v21.2.15`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#21215-2026-05-28) [Compare Source](https://github.com/angular/angular/compare/v21.2.14...v21.2.15) ##### common | Commit | Type | Description | | -- | -- | -- | | [7f4ac78994](https://github.com/angular/angular/commit/7f4ac78994bff1576ab33f3ce48f95c17f40b4d8) | fix | add upper bounds for digitsInfo | | [300f61feb3](https://github.com/angular/angular/commit/300f61feb3a534bfddf16fcbd240f97b32249699) | fix | sanitize placeholder | ##### compiler | Commit | Type | Description | | -- | -- | -- | | [0b07f47bd6](https://github.com/angular/angular/commit/0b07f47bd6598ae6bd5b75a375e2c817a3c0f243) | fix | normalize tag names with custom namespaces in DomElementSchemaRegistry ([#​68925](https://github.com/angular/angular/pull/68925)) | | [eb1cbbf2eb](https://github.com/angular/angular/commit/eb1cbbf2eb5833219a367a61c04eb07aaa36cc29) | fix | prevent namespaced SVG <style> elements from being stripped | | [cc1378d54b](https://github.com/angular/angular/commit/cc1378d54bd93f3882d732261be8e66720eb71b2) | fix | sanitize dynamic href and xlink:href bindings on SVG a elements ([#​68925](https://github.com/angular/angular/pull/68925)) | | [782e01594e](https://github.com/angular/angular/commit/782e01594e2ad9134c7385dcf3b518101b23ccab) | fix | strip namespaced SVG script elements during template compilation ([#​68925](https://github.com/angular/angular/pull/68925)) | ##### core | Commit | Type | Description | | -- | -- | -- | | [ff12fe55ac](https://github.com/angular/angular/commit/ff12fe55ace5e861ba261afb4c0480ff3c40a192) | fix | normalize tag names in runtime i18n attribute security context lookup ([#​68925](https://github.com/angular/angular/pull/68925)) | | [e6fe77cc97](https://github.com/angular/angular/commit/e6fe77cc97fd10351687416f938bf754aff4eb9f) | fix | sanitize meta selectors | | [daaf32937f](https://github.com/angular/angular/commit/daaf32937fd5c46e411b26f7c082613716fe9550) | fix | support prefix-insensitive DOM schema lookups and compile-time i18n attribute validation ([#​68925](https://github.com/angular/angular/pull/68925)) | | [dada86e43d](https://github.com/angular/angular/commit/dada86e43d847204f714d1a933084617ab941c0a) | fix | synchronize core sanitization schema with compiler ([#​68925](https://github.com/angular/angular/pull/68925)) | ##### http | Commit | Type | Description | | -- | -- | -- | | [582a417bd2](https://github.com/angular/angular/commit/582a417bd27fdaf989e5065dbcdf1ad752faf70c) | fix | exclude withCredentials requests from transfer cache | | [5c6d6df34b](https://github.com/angular/angular/commit/5c6d6df34bbeff3ce98f3b35875444f925cc8f51) | fix | skip TransferCache for cookie-bearing requests by default | ##### platform-server | Commit | Type | Description | | -- | -- | -- | | [37e8aadf87](https://github.com/angular/angular/commit/37e8aadf87b4facfcaf002a1557f8c393a362d97) | fix | prevent SSRF bypasses via backslash URLs in HttpClient | | [72696e244e](https://github.com/angular/angular/commit/72696e244ed7646cca9ab9afc7769a2163943bda) | fix | secure location and document initialization against SSRF and path hijack | ##### service-worker | Commit | Type | Description | | -- | -- | -- | | [b8bd49341d](https://github.com/angular/angular/commit/b8bd49341ddcee10d119a9d4aa8e5736e4e5da53) | fix | Preserves explicit 'credentials: omit' in asset requests | | [ca32fc1000](https://github.com/angular/angular/commit/ca32fc10001301e6174804f9abcfba62252334f4) | fix | Preserves HTTP cache mode in asset group requests | <!-- CHANGELOG SPLIT MARKER --> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #247 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
67d04b0a78 |
fix(deps): update nestjs (#246)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@nestjs/common](https://nestjs.com) ([source](https://github.com/nestjs/nest/tree/HEAD/packages/common)) | dependencies | patch | [`11.1.21` -> `11.1.24`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.21/11.1.24) | | [@nestjs/core](https://nestjs.com) ([source](https://github.com/nestjs/nest/tree/HEAD/packages/core)) | dependencies | patch | [`11.1.21` -> `11.1.24`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.21/11.1.24) | | [@nestjs/platform-express](https://nestjs.com) ([source](https://github.com/nestjs/nest/tree/HEAD/packages/platform-express)) | dependencies | patch | [`11.1.21` -> `11.1.24`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-express/11.1.21/11.1.24) | | [@nestjs/swagger](https://github.com/nestjs/swagger) | dependencies | patch | [`11.4.3` -> `11.4.4`](https://renovatebot.com/diffs/npm/@nestjs%2fswagger/11.4.3/11.4.4) | | [@nestjs/testing](https://nestjs.com) ([source](https://github.com/nestjs/nest/tree/HEAD/packages/testing)) | devDependencies | patch | [`11.1.21` -> `11.1.24`](https://renovatebot.com/diffs/npm/@nestjs%2ftesting/11.1.21/11.1.24) | --- ### Release Notes <details> <summary>nestjs/nest (@​nestjs/common)</summary> ### [`v11.1.24`](https://github.com/nestjs/nest/releases/tag/v11.1.24) [Compare Source](https://github.com/nestjs/nest/compare/v11.1.23...v11.1.24) ##### v11.1.24 (2026-05-25) ##### Bug fixes - `core` - [#​17009](https://github.com/nestjs/nest/pull/17009) fix(core): reset dependency-tree cache on metadata changes ([@​puneetdixit200](https://github.com/puneetdixit200)) ##### Enhancements - `core` - [#​16997](https://github.com/nestjs/nest/pull/16997) feat(core): warn on late websocket adapter registration ([@​hbinhng](https://github.com/hbinhng)) ##### Dependencies - `platform-ws` - [#​17011](https://github.com/nestjs/nest/pull/17011) chore(deps): bump ws from 8.20.1 to 8.21.0 ([@​dependabot\[bot\]](https://github.com/apps/dependabot)) ##### Committers: 2 - Nguyễn Hải Bình ([@​hbinhng](https://github.com/hbinhng)) - Puneet Dixit ([@​puneetdixit200](https://github.com/puneetdixit200)) ### [`v11.1.23`](https://github.com/nestjs/nest/releases/tag/v11.1.23) [Compare Source](https://github.com/nestjs/nest/compare/v11.1.22...v11.1.23) ##### v11.1.23 (2026-05-21) ##### Bug fixes - `core` - https://github.com/nestjs/nest/issues/16998 fix snapshot: true eagerly instantiates Terminus transient indicators since 11.1.20 ##### Committers: 1 - Kamil Mysliwiec ([@​kamilmysliwiec](https://github.com/kamilmysliwiec)) ### [`v11.1.22`](https://github.com/nestjs/nest/releases/tag/v11.1.22) [Compare Source](https://github.com/nestjs/nest/compare/v11.1.21...v11.1.22) ##### v11.1.22 (2026-05-21) ##### Bug fixes - `core` - [#​16993](https://github.com/nestjs/nest/pull/16993) fix(core): inflight request injection bug [#​16989](https://github.com/nestjs/nest/issues/16989) ([@​kamilmysliwiec](https://github.com/kamilmysliwiec)) ##### Enhancements - `core` - [#​16967](https://github.com/nestjs/nest/pull/16967) fix(core): identify decorator type in invalid-class-module error ([@​HarrierOnChain](https://github.com/HarrierOnChain)) - ##### Committers: 2 - Harrier ([@​HarrierOnChain](https://github.com/HarrierOnChain)) - Kamil Mysliwiec ([@​kamilmysliwiec](https://github.com/kamilmysliwiec)) </details> <details> <summary>nestjs/swagger (@​nestjs/swagger)</summary> ### [`v11.4.4`](https://github.com/nestjs/swagger/releases/tag/11.4.4) [Compare Source](https://github.com/nestjs/swagger/compare/11.4.3...11.4.4) #### 11.4.4 (2026-05-21) ##### Bug fixes - [#​3930](https://github.com/nestjs/swagger/pull/3930) fix: top-level nullable with discriminator issue ([@​kamilmysliwiec](https://github.com/kamilmysliwiec)) ##### Enhancements - [#​3921](https://github.com/nestjs/swagger/pull/3921) feat(swagger): add summary field to Tag Object (OpenAPI 3.2) ([@​frbuceta](https://github.com/frbuceta)) - [#​3924](https://github.com/nestjs/swagger/pull/3924) feat(swagger): warn when [@​ApiTags](https://github.com/ApiTags) receives hierarchy fields ([@​frbuceta](https://github.com/frbuceta)) - [#​3925](https://github.com/nestjs/swagger/pull/3925) fix(swagger): type Tag Object kind as a free-form string ([@​frbuceta](https://github.com/frbuceta)) ##### Committers: 4 - Alexander Scholz ([@​LucidityDesign](https://github.com/LucidityDesign)) - Francisco Buceta ([@​frbuceta](https://github.com/frbuceta)) - Kamil Mysliwiec ([@​kamilmysliwiec](https://github.com/kamilmysliwiec)) - Natanael dos Santos Feitosa ([@​natanfeitosa](https://github.com/natanfeitosa)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/246 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
0bdc065eb4 |
fix(deps): update dependency nestjs-cls to v6.2.1 (#245)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [nestjs-cls](https://papooch.github.io/nestjs-cls/) ([source](https://github.com/Papooch/nestjs-cls)) | dependencies | patch | [`6.2.0` -> `6.2.1`](https://renovatebot.com/diffs/npm/nestjs-cls/6.2.0/6.2.1) | --- ### Release Notes <details> <summary>Papooch/nestjs-cls (nestjs-cls)</summary> ### [`v6.2.1`](https://github.com/Papooch/nestjs-cls/releases/tag/nestjs-cls%406.2.1) [Compare Source](https://github.com/Papooch/nestjs-cls/compare/nestjs-cls@6.2.0...nestjs-cls@6.2.1) ##### Bug Fixes - **core**: establish ALS root context at init to mitigate enterWith leak on Node < 24 ([#​577](https://github.com/Papooch/nestjs-cls/issues/577)) ([05b8fb0](https://github.com/Papooch/nestjs-cls/commits/05b8fb0)) - **core**: improve ClsPluginsHooksHost error message and document app.init() requirement ([#​578](https://github.com/Papooch/nestjs-cls/issues/578)) ([2a4e874](https://github.com/Papooch/nestjs-cls/commits/2a4e874)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/245 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
c41f0a9af9 |
fix(deps): update angular to v21.2.13 (#244)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular-devkit/core](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@angular-devkit%2fcore/21.2.12/21.2.13) | | [@angular-devkit/schematics](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@angular-devkit%2fschematics/21.2.12/21.2.13) | | [@angular/build](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@angular%2fbuild/21.2.12/21.2.13) | | [@angular/cdk](https://github.com/angular/components) | dependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@angular%2fcdk/21.2.12/21.2.13) | | [@angular/cli](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@angular%2fcli/21.2.12/21.2.13) | | [@schematics/angular](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.12` -> `21.2.13`](https://renovatebot.com/diffs/npm/@schematics%2fangular/21.2.12/21.2.13) | --- ### Release Notes <details> <summary>angular/angular-cli (@​angular-devkit/core)</summary> ### [`v21.2.13`](https://github.com/angular/angular-cli/blob/HEAD/CHANGELOG.md#21213-2026-05-27) [Compare Source](https://github.com/angular/angular-cli/compare/v21.2.12...v21.2.13) ##### [@​angular-devkit/build-angular](https://github.com/angular-devkit/build-angular) | Commit | Type | Description | | --------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------------- | | [3c6d26a31](https://github.com/angular/angular-cli/commit/3c6d26a316cd6aea455c19b249dc6852d84a698e) | fix | remove unconditional CORS wildcard from webpack dev-server | ##### [@​angular/build](https://github.com/angular/build) | Commit | Type | Description | | --------------------------------------------------------------------------------------------------- | ---- | ------------------------------------------------------- | | [2b3e95517](https://github.com/angular/angular-cli/commit/2b3e95517358f8ef3482d5319d970f4774e45ad0) | fix | assert that asset input paths are within workspace root | <!-- CHANGELOG SPLIT MARKER --> </details> <details> <summary>angular/components (@​angular/cdk)</summary> ### [`v21.2.13`](https://github.com/angular/components/blob/HEAD/CHANGELOG.md#21213-21-2-13-2026-05-27) [Compare Source](https://github.com/angular/components/compare/v21.2.12...v21.2.13) No user facing changes in this release <!-- CHANGELOG SPLIT MARKER --> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #244 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
c5704ef2af |
chore(deps): update dependency dayjs to v1.11.21 (#242)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [dayjs](https://day.js.org) ([source](https://github.com/iamkun/dayjs)) | devDependencies | patch | [`1.11.20` -> `1.11.21`](https://renovatebot.com/diffs/npm/dayjs/1.11.20/1.11.21) | --- ### Release Notes <details> <summary>iamkun/dayjs (dayjs)</summary> ### [`v1.11.21`](https://github.com/iamkun/dayjs/blob/HEAD/CHANGELOG.md#11121-2026-05-26) [Compare Source](https://github.com/iamkun/dayjs/compare/v1.11.20...v1.11.21) ##### Bug Fixes - preserve unsupported year tokens in format ([#​3015](https://github.com/iamkun/dayjs/issues/3015)) ([#​3016](https://github.com/iamkun/dayjs/issues/3016)) ([8fda602](https://github.com/iamkun/dayjs/commit/8fda602beac5abbc64230ddc49085aa532320f26)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/242 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
e88263a985 |
fix(ci): give gitlab perf job a discoverable chromium (ADR-0028) (#243)
## Summary Fix the GitLab `perf` job, which failed at the Lighthouse CI healthcheck with `Chrome installation not found`. Follow-up on [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 (`.gitlab-ci.yml` landed in #241). ## Root cause The `perf` job ran on `mcr.microsoft.com/playwright:v1.55.0-jammy`. That image **does** ship Chromium, but under `/ms-playwright/chromium-<rev>/chrome-linux/chrome` — a non-standard path/name. Lighthouse CI uses `chrome-launcher`, which discovers Chrome by probing the `PATH` (`google-chrome`, `chromium`, `chromium-browser`) or the `CHROME_PATH` env var. It cannot find Playwright's bundled Chromium, so the healthcheck fails before any run starts. The same class of failure ("absence de Chrome pour Lighthouse") was hit and solved on the Gitea side — there the `catthehacker/ubuntu:full-22.04` image happened to carry Chrome at a standard location. ## Fix Decouple the two browser-driving tools instead of forcing one image to serve both: | Aspect | Before | After | | --- | --- | --- | | `image` | `mcr.microsoft.com/playwright:v1.55.0-jammy` | `node:24-bookworm` (same as the `.node-job` base) | | Chrome | bundled, undiscoverable | `apt-get install --no-install-recommends chromium` → `/usr/bin/chromium` | | `CHROME_PATH` | unset | `/usr/bin/chromium` (explicit, removes any PATH ambiguity) | | `before_script` | inherited | `!reference [.node-job, before_script]` + the apt install | Rationale for not pinning Playwright's Chromium via `CHROME_PATH` instead: that couples the perf job to an exact match between the npm `@playwright/test` version and the Docker image tag. A Renovate bump of `@playwright/test` while the image stays at `v1.55.0` would silently break `executablePath()`. The apt-installed Debian `chromium` has no such coupling. The future ADR-0016 axe-core e2e job — which drives **Playwright** directly — will use the Playwright image. Lighthouse wants a standard Chrome; Playwright wants its own browsers. Different tools, different images. `lighthouserc.js` already passes `--no-sandbox` (containerised-CI requirement), so no change needed there — that flag's rationale applies identically on GitLab Runner. ## Trade-off noted The `apt-get install chromium` re-downloads ~100 MB per perf run (the package lands in the job container's ephemeral writable layer, not a cached image layer). Acceptable for now. If perf-job duration or `vm-gitlab` bandwidth becomes a pain, the clean optimisation is a small custom image with Chromium pre-installed, pushed to GitLab's Container Registry — out of scope here, flagged for later. ## Test plan - [ ] GitLab `perf` job reaches the Lighthouse run (no more `Chrome installation not found`) and the ADR-0017 assertions evaluate. - [ ] `chromium` apt install completes on `node:24-bookworm` (Debian bookworm `main` carries the package). - [ ] `.gitlab-ci.yml` passes GitLab CI Lint (Project → Build → Pipeline Editor → Validate) — confirms the `!reference` + added keys parse. - [ ] Other jobs (`check`, `audit`, `commits`, `a11y`, `sast`, `secret_detection`) unaffected. ## Related - [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 — `.gitlab-ci.yml` (#241). - [ADR-0017](../docs/decisions/0017-performance-budgets-lighthouse-ci.md) — Lighthouse CI perf budgets. - [ADR-0016](../docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md) — future axe-core e2e (will own the Playwright image). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #243 |
||
|
|
c24795f1d6 |
ci(gitlab): land .gitlab-ci.yml alongside gitea workflow (ADR-0028 phase 2) (#241)
## Summary Bump the existing `tmp` security override from `>=0.2.4` to `>=0.2.6` to cover [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — a path-traversal vulnerability via unsanitized prefix/postfix in `tmp@<0.2.6`. Surfaced by `pnpm ci:audit` on the next CI run; both Gitea and the in-flight GitLab Phase 2 pipeline fail without this. ## Why this isn't an upstream upgrade `tmp` is a transitive dependency. The two consuming paths today: | Path | Latest available | | --- | --- | | `.>nx>tmp` | `nx@22.7.4` still declares `tmp@^0.2.4` | | `.>@lhci/cli>tmp` | `@lhci/cli@0.15.1` (latest) still declares `tmp@^0.1.0` | Upgrading `nx` or `@lhci/cli` does not move `tmp` to 0.2.6. The pre-existing `pnpm.overrides` entry was already pinning `tmp@<0.2.4` → `>=0.2.4` against the prior advisory; this PR just widens the lower bound to match the new one. Same pattern, one line. ## What lands | File | Change | | --- | --- | | `package.json` | `"tmp@<0.2.4": ">=0.2.4"` → `"tmp@<0.2.6": ">=0.2.6"` (one line). | | `pnpm-lock.yaml` | `tmp@0.2.4` → `tmp@0.2.6` resolved; `@scalar/nestjs-api-reference@1.1.16 → 1.1.19` picked up as a natural transitive resolution during `pnpm install` (no semver-major, both are within the existing `^1.1.14` range and match Renovate's "patch + auto-merge" policy). | ## Risk Patch bump (0.2.4 → 0.2.6) on a tiny library with a stable public API since v0.2.0. The release notes for 0.2.5 and 0.2.6 are sanitization-only fixes — no API surface change. Risk of regression in nx / lhci consumers: negligible. ## Test plan - [x] `pnpm audit --audit-level=moderate` returns `No known vulnerabilities found` (exit 0). - [x] `pnpm why tmp` shows `tmp@0.2.6` as the single resolved version (no duplicate). - [ ] CI green on Gitea (`check` + `scan` jobs). - [ ] Once merged, rebase `ci/gitlab-pipeline-phase2` on top and retry the GitLab `audit` job — expected green. ## Related - [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — the advisory. - Unblocks [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 pipeline parity validation (see `ci/gitlab-pipeline-phase2` branch). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #241 |
||
|
|
a848e0fabd |
chore(deps): update dependency @swc/helpers to v0.5.23 (#239)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@swc/helpers](https://swc.rs) ([source](https://github.com/swc-project/swc/tree/HEAD/packages/helpers)) | devDependencies | patch | [`0.5.21` -> `0.5.23`](https://renovatebot.com/diffs/npm/@swc%2fhelpers/0.5.21/0.5.23) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #239 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
e2894afc4a |
chore(deps): bump tmp override to >=0.2.6 (GHSA-ph9p-34f9-6g65) (#240)
## Summary Bump the existing `tmp` security override from `>=0.2.4` to `>=0.2.6` to cover [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — a path-traversal vulnerability via unsanitized prefix/postfix in `tmp@<0.2.6`. Surfaced by `pnpm ci:audit` on the next CI run; both Gitea and the in-flight GitLab Phase 2 pipeline fail without this. ## Why this isn't an upstream upgrade `tmp` is a transitive dependency. The two consuming paths today: | Path | Latest available | | --- | --- | | `.>nx>tmp` | `nx@22.7.4` still declares `tmp@^0.2.4` | | `.>@lhci/cli>tmp` | `@lhci/cli@0.15.1` (latest) still declares `tmp@^0.1.0` | Upgrading `nx` or `@lhci/cli` does not move `tmp` to 0.2.6. The pre-existing `pnpm.overrides` entry was already pinning `tmp@<0.2.4` → `>=0.2.4` against the prior advisory; this PR just widens the lower bound to match the new one. Same pattern, one line. ## What lands | File | Change | | --- | --- | | `package.json` | `"tmp@<0.2.4": ">=0.2.4"` → `"tmp@<0.2.6": ">=0.2.6"` (one line). | | `pnpm-lock.yaml` | `tmp@0.2.4` → `tmp@0.2.6` resolved; `@scalar/nestjs-api-reference@1.1.16 → 1.1.19` picked up as a natural transitive resolution during `pnpm install` (no semver-major, both are within the existing `^1.1.14` range and match Renovate's "patch + auto-merge" policy). | ## Risk Patch bump (0.2.4 → 0.2.6) on a tiny library with a stable public API since v0.2.0. The release notes for 0.2.5 and 0.2.6 are sanitization-only fixes — no API surface change. Risk of regression in nx / lhci consumers: negligible. ## Test plan - [x] `pnpm audit --audit-level=moderate` returns `No known vulnerabilities found` (exit 0). - [x] `pnpm why tmp` shows `tmp@0.2.6` as the single resolved version (no duplicate). - [ ] CI green on Gitea (`check` + `scan` jobs). - [ ] Once merged, rebase `ci/gitlab-pipeline-phase2` on top and retry the GitLab `audit` job — expected green. ## Related - [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — the advisory. - Unblocks [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 pipeline parity validation (see `ci/gitlab-pipeline-phase2` branch). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #240 |
||
|
|
3191040bbc |
chore(gitlab): add MR template (ADR-0028 phase 1) (#238)
## Summary [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 1 (`mirror-and-bootstrap`) — repo-side groundwork for the GitLab CE migration. Phase 1 is mostly ops work on `vm-gitlab` (groups, mirror push, branch protection, bot account); the only artefact that needs to land in the codebase ahead of the cutover is the GitLab MR template, so that the moment Phase 2 lands `.gitlab-ci.yml` and developers start opening MRs, they get the same structured prompt Gitea PRs use today. ## What lands - **New file `.gitlab/merge_request_templates/Default.md`** — port of `.gitea/pull_request_template.md`, content-identical except for two adjustments: - "PR title" → "MR title" in the comment header (GitLab terminology). - **Typo fix:** the section reference to the Conventional Commits convention was `docs/development.md §5` in the original (which is "Observability dev-loop"); the correct section is **§7 "Conventional commit cycle"**. The Gitea template still has the wrong ref — it gets deleted in Phase 3 cutover so fixing it there rather than touching it twice. GitLab discovers `.gitlab/merge_request_templates/*.md` automatically; `Default.md` becomes the default for new MRs without any project-settings step. ## Out of scope (per [ADR-0028 §"Migration sequence"](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md)) | Item | Phase | | --- | --- | | `.gitlab-ci.yml` | 2 (parallel pipelines) | | `renovate.json` `customManagers.fileMatch` swap from `.gitea/workflows/*.yml` → `.gitlab-ci.yml` | 2-3 (after `.gitlab-ci.yml` exists) | | Remote URLs in CLAUDE.md / `docs/setup/01-dev-debian-vm-setup.md` §8 | 3 (cutover) | | Deletion of `.gitea/workflows/` + `infra/ci-runners.compose.yml` + `infra/data/runner-*/` | 3 (cutover) | | Stale-reference sweep + signed-commit policy | 4 (cleanup) | ## Ops checklist (executed on `vm-gitlab` — not part of this PR) For traceability; the following happens outside the repo, on `vm-gitlab` (`10.100.201.10`, LAN-only — VPN for off-site access is sufficient): - [ ] Create group `apf-portal` + project `apf_portal` on GitLab CE. - [ ] `git remote add gitlab git@10.100.201.10:apf-portal/apf_portal.git && git push --mirror gitlab` from a current clone. - [ ] Configure branch protection on `main`: push direct disabled, squash-merge only, status checks list left empty (peuplé en Phase 2 quand `.gitlab-ci.yml` apparaît). - [ ] Create `apf-portal-bot` service account on GitLab, issue a dedicated SSH/GPG signing key (cf. ADR-0028 §"Signed commits"), generate a Renovate platform token. - [ ] Reconfigure the existing Renovate runtime (host config, not in this repo) to add the GitLab platform endpoint pointed at `10.100.201.10`. Renovate's `config:recommended` + the `renovate.json` in-repo are platform-agnostic; only the bot's host config needs the new endpoint + token. ## Test plan - [x] `.gitlab/merge_request_templates/Default.md` markdown renders identically to the Gitea template in a local preview. - [x] No CI gates touched — `pnpm ci:check` / `ci:audit` unchanged. - [ ] On first MR opened post-mirror, the Default template appears auto-selected. ## Related - [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) — migration ADR (accepted in #227). - Next: Phase 2 (`gitlab-ci-pipeline`) once `vm-gitlab` ops bootstrap is complete and the runner is registered. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #238 |
||
|
|
8bc36b460a |
fix(admin): unbreak ci:check on PR2b (TS2454 + missing fr translation) (#237)
## Summary Follow-up fix on [#236](#236) (ADR-0026 PR 2b). Two issues that `nx run-many -t build` exposed but `nx run-many -t test` masked: 1. **TS2454 in `user-scopes.service.ts`** — webpack's strict-mode build flagged `let exists: boolean` as "used before assigned". jest's `--transpile-only` doesn't enforce definite-assignment so it slipped through local checks. 2. **Missing FR translation** for `route.user-scopes.title` — Angular's `@angular/localize` build target requires every `$localize` ID to have a target in `messages.fr.xlf`. The route I added in #236 referenced the ID but I didn't add the trans-unit. ## What lands | File | Change | | --- | --- | | `apps/portal-bff/src/admin/user-scopes.service.ts` | Initialise `let exists = false` instead of `let exists: boolean`. The `VALUE_BEARING_KINDS.has(kind)` check earlier in the function does narrow `kind` semantically, but `Set.has` doesn't propagate type narrowing the way `Array.includes` or a custom type guard would. Initialising to `false` is the smallest fix; the next `if (!exists)` still throws the friendly "does not match" message in the (unreachable-by-construction) fallback. | | `apps/portal-admin/src/locale/messages.fr.xlf` | New `<trans-unit id="route.user-scopes.title">` — FR target `Périmètres utilisateur — Administration APF Portal`. | ## Why these failed locally - **TS2454**: jest runs with `--transpile-only` so it skips definite-assignment analysis (and most type-checking). Webpack's production build runs full `tsc` and catches it. - **Missing translation**: the dev server `pnpm nx serve portal-admin` doesn't run the localize pass; only `--configuration=production` does. My local `nx test portal-admin` runs in JIT mode against the source locale, so the missing FR target slid through. For the future: `pnpm nx run-many -t build --projects=portal-bff,portal-admin` would have caught both pre-PR. Worth a `pnpm ci:check:fast` shorthand that runs a subset of the CI gates locally before push — but that's a separate PR. ## Test plan - [x] `pnpm exec nx run-many -t build --projects=portal-bff,portal-admin` — both pass. - [x] No application logic changed in either fix (initialise vs declare; add a missing translation row). - [ ] CI green on this PR. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #237 |
||
|
|
928ed0cdc2 |
feat(admin): add /admin/users/:oid/scopes screen + endpoints (ADR-0026 PR 2b) (#236)
## Summary ADR-0026 PR 2b (second half of PR 2). Ships the operator surface for the `@RequireScope` stack — the admin Angular screen at `/admin/users/:oid/scopes` lets an admin list / grant / revoke scopes for an existing portal User. Both write paths emit blocking audit rows per ADR-0013 (`admin.scope_granted` / `admin.scope_revoked`). Closes the ADR-0026 trilogy: | PR | Status | Effect | | --- | --- | --- | | ADR-0026 PR 1 (#232) | merged | Person + User + UserScope schema + provisioner + drift gate Person.source + PrincipalBuilder real UUIDs. | | ADR-0026 PR 2a (#233) | merged | PrismaScopeResolver replaces stub + prisma/seed.ts populates 19 personas. | | **ADR-0026 PR 2b (this)** | proposed | Admin scope-management screen — operator surface. | ## What lands ### BFF (under `apps/portal-bff/src/`) | File | Change | | --- | --- | | `admin/user-scopes.dto.ts` | **New**. `GrantUserScopeDto` (class-validator on kind + value + ISO expiresAt) + response shapes (`UserScopeDto`, `UserScopesPageDto`). | | `admin/user-scopes.service.ts` | **New**. `resolveUserByOid` (404 when no portal User), `list` (`UserScope` rows joined to User+Person), `grant` (validates value against ADR-0027 tables for value-bearing kinds, rejects non-empty for valueless, P2002 → 400), `revoke` (404-protected — won't leak scope-belongs-to-other-user). | | `admin/user-scopes.controller.ts` | **New** REST surface at `/api/admin/users/:oid/scopes` with `@RequireAdmin`. `GET` (no audit, read), `POST` (audit `admin.scope_granted`), `DELETE :scopeId` (audit `admin.scope_revoked`, 204 on success). | | `admin/user-scopes.service.spec.ts` + `admin/user-scopes.controller.spec.ts` | **New** specs — 15 cases across resolve / list / grant (value-bearing + valueless + duplicate) / revoke / audit semantics. | | `admin/admin.module.ts` | Registers `UserScopesController` + `UserScopesService`. | | `audit/audit.types.ts` + `audit/audit.service.ts` | New `AdminScopeGrantedInput` / `AdminScopeRevokedInput` types + `adminScopeGranted` / `adminScopeRevoked` methods on `AuditWriter`. `subject = 'user:<oid>'` so an auditor pivots on the target of the change; payload carries the resolved tuple at the moment of the write (the revoke payload survives the row's deletion). | ### SPA (under `apps/portal-admin/src/app/`) | File | Change | | --- | --- | | `app.routes.ts` | New route `/users/:oid/scopes` (lazy-loaded). | | `pages/user-scopes/user-scopes.service.ts` + `.service.spec.ts` | **New** thin HttpClient wrapper (`list` / `grant` / `revoke`). Same shape convention as `AdminUsersService`. | | `pages/user-scopes/user-scopes.ts` + `.html` + `.scss` + `.spec.ts` | **New** page component. Signals-based + OnPush. Lists current scopes in a table with `Revoke` per row; "Grant a new scope" form with kind dropdown + conditional value input + optional `expiresAt`. Friendly error mapping (403 → "no admin access", 404 → "User not found, has this persona ever signed in or been seeded?", server messages forwarded). | | `pages/users/users.html` | New `Manage scopes` link per row (RouterLink to the new page), with `aria-label` carrying the user's displayName. | | `pages/users/users.ts` + `users.spec.ts` | Adds `RouterLink` import + `provideRouter([])` in the spec fixture (was missing — the route addition exposed it). | ## Key choices - **`:oid` in the URL, not `User.id`.** The existing `/admin/users` list (ADR-0020) keys on Entra `oid` and the new screen is its child — linking from the list to the scopes page without an intermediate UUID lookup is the simplest UX. The BFF translates `oid → User.id` internally via `resolveUserByOid`. - **404 carries a hint.** When `resolveUserByOid` misses, the BFF throws `NotFoundException("No portal User found for Entra oid ${oid}.")` and the SPA translates the 403/404 status to friendly French-English text. The hint mentions "has this persona ever signed in or been seeded?" — a common operator confusion (the user-directory list shows oids that signed in but the `User` row only exists post-sign-in OR post-seed). - **Audit before write commit, write after audit success.** Standard ADR-0013 posture. Order in the controller: service call (which writes the DB row) → audit (blocking). If audit fails, the DB row exists but no audit — a known v1 cost for non-rollback flows. The simpler alternative (audit before write) would emit ghost audit rows for failed writes; we picked the cleaner direction. - **Value-bearing validation against ADR-0027 tables, not catalogue.** `etablissement:0330800013` must match an existing `Structure.code`; `delegation:33` must match `Delegation.code`; `region:75` must match `Region.code`. The lookup is a single `findUnique` per kind; rejection raises 400 with the offending value in the message. Valueless kinds (`self` / `siege` / `unrestricted`) reject any non-empty value as a defensive check (the DTO type already constrains this but the service runs the assertion). - **`UserScope.source = 'admin-ui'`** for every row created by this surface. Distinct from `'seed'` (set by `prisma/seed.ts`) and `'self-signin'` (Person source only). ADR-0029's syncs will add `'pleiades'` / `'acteurs-plus'`. - **A11y per [ADR-0016](https://git.unespace.com/julien/apf_portal/src/branch/main/docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md)**: `aria-labelledby` on the form sections, `role="status"`/`aria-live="polite"` for load + error feedback, `role="alert"` for submit errors, `min-height: 44px` on every input/button per the touch-target rule, `aria-label` on the Manage-scopes link carrying the displayName for screen readers, conditional `aria-required` + `aria-describedby` on the value input. - **`window.confirm` before revoke.** Cheap protection against accidental keyboard-activation. v1 OK; a future polish PR could replace with the spartan-ng dialog when it ships. ## Local verification - [x] `node scripts/check-catalogue-drift.mjs` — clean (4 / 24 / 7 / 3). - [x] `pnpm exec nx lint portal-bff` — **0 errors** (13 warnings, all pre-existing). - [x] `pnpm exec nx lint portal-admin` — **0 errors** (3 warnings, all pre-existing). - [x] `pnpm exec nx test portal-bff` — full suite passes (now includes the 2 new `user-scopes` spec files). - [x] `pnpm exec nx test portal-admin` — **71 tests passing** (added 8 for `user-scopes`; updated `users.spec.ts` to provide a router after the RouterLink import). ## Test plan — remaining (manual on dev DB) - [ ] **Sign in as `admin@apfrd...`** (the only persona with `Portal.Admin`) on `portal-admin`. Navigate to `/admin/users`. Click `Manage scopes` for `directeur-bordeaux`. The page shows one row `etablissement:0330800013` (from the seed). - [ ] **Grant a new scope**: kind `delegation`, value `33`. Submit. New row appears; audit row `admin.scope_granted` lands in `audit.events`. - [ ] **Try a bogus value** (kind `etablissement`, value `xyz`): server returns 400, form shows the message. - [ ] **Try a duplicate** (`etablissement:0330800013` again on `directeur-bordeaux`): server returns 400 with "already granted". - [ ] **Revoke a scope**: row disappears; audit row `admin.scope_revoked` lands. - [ ] **Sign in as a non-admin persona** (e.g. `collaborateur-simple`) and try `/admin/users/.../scopes` → 403 + friendly error. - [ ] **Review focus** — the BFF's `validateValueForKind` switch, the SPA's `translateError` mapping, the a11y attributes on the form, the audit ordering in the controller methods. ## Notes for the reviewer - **No scope-vocabulary endpoint.** The form expects the operator to type the `Structure.code` / `Delegation.code` / `Region.code` by hand (with a hint string under the field). Defensive validation catches typos. A future polish PR could add `GET /api/admin/scope-vocabulary` returning all three lists at once + replace the value input with a typeahead dropdown. - **Renamed the service's `UserScopesPage` interface to `UserScopesPayload`.** Discovered the collision with the component class during the first test run. The component keeps the `UserScopesPage` name per Angular convention; the payload type now reads as what it is. - **`UserScope.source` is not under the drift gate today.** The schema comment in PR 1 said "same catalogue posture as Person.source"; in practice the seed writes `'seed'` and this PR writes `'admin-ui'`. Extending the drift gate to cover `UserScope.source` is a small follow-up — defensible to do once ADR-0029's upstream-sync values are landing. ## What's next ADR-0025's `@RequireScope` stack is now end-to-end live for the test tenant: persona → Entra → BFF → PrismaScopeResolver → DB → Principal → guard. The trilogy is done. Per the prior conversation: **GitLab migration Phase 1** (`mirror-and-bootstrap`) is the next item — mostly ops work on `vm-gitlab` (groups, branch protection, Renovate reconfig, deploy keys). The first PR-shaped output from that phase is the Renovate config update. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #236 |
||
|
|
55338b83c0 |
fix(seed): use tsconfig.app.json instead of inline --compiler-options (#235)
## Summary Follow-up to [#234](#234). The `db:seed` script's inline `--compiler-options '{"module":"CommonJS",…}'` was getting its quotes stripped by the shell when `pnpm run` re-spawned `ts-node`: ``` > ts-node --compiler-options {module:CommonJS,esModuleInterop:true,target:ES2020} apps/portal-bff/prisma/seed.ts SyntaxError: Expected property name or '}' in JSON at position 1 ``` Cross-platform JSON-on-cmdline quoting through nested `package.json → shell → npm-script → shell → ts-node` is a notorious pain. `apps/portal-bff/tsconfig.app.json` already declares everything `ts-node` needs to transpile the seed (module=commonjs, esModuleInterop=true, target=es2021, moduleResolution=node, types=[node]). Point at it with `-P` and drop the inline JSON entirely. ## What lands | File | Change | | --- | --- | | `package.json` (root) | `db:seed` script: `--compiler-options '{...}'` → `-P apps/portal-bff/tsconfig.app.json`. One-line diff. | That's it. ## Why `tsconfig.app.json` works for the seed even though seed.ts isn't under `src/` `ts-node --transpile-only` reads `compilerOptions` from the `-P` tsconfig but **ignores the `include` / `exclude` fields** for the file passed explicitly on the command line. So the fact that `tsconfig.app.json` has `"include": ["src/**/*.ts"]` and the seed lives under `prisma/` doesn't matter — `ts-node` happily transpiles whatever file you point it at. ## Test plan - [ ] **Locally**: `cd apps/portal-bff && pnpm exec prisma db seed` — no more `SyntaxError` on the compiler-options, seed runs and reports `[seed] test-tenant complete …`. - [ ] **Idempotency** — re-run, expect `created 0 / 0 / 0`. - [x] No application code changed — drift gate / lint / tests untouched. ## Notes for the reviewer - Could have created a dedicated `apps/portal-bff/prisma/tsconfig.json` extending the app config. Less coupling but more files; pointing at the existing `tsconfig.app.json` is the minimum-surface fix. - The Prisma 7 deprecation warning about `package.json#prisma` is unchanged; migrating to `prisma.config.ts` is a separate future PR (would also let us inline the seed config without shell quoting at all). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #235 |
||
|
|
827d69594c |
fix(seed): route prisma db seed through pnpm run for cwd resolution (#234)
## Summary Follow-up fix on [#233](#233). The `prisma db seed` command failed with `Cannot find module './seed.ts'` because the seed command's path was resolved against the user's cwd (`apps/portal-bff/`) and doubled into `apps/portal-bff/apps/portal-bff/prisma/seed.ts`. Root cause: Prisma CLI spawns the seed command with cwd = wherever the user invoked `prisma db seed` from. The path `apps/portal-bff/prisma/seed.ts` only works if cwd is the workspace root, but the user's habit (matching `prisma migrate dev`) is to run prisma commands from `apps/portal-bff/`. Fix: indirect through `pnpm run`. `pnpm run` for a root-level script always sets cwd = workspace root regardless of where invoked from. The seed path then resolves predictably. ## What lands | File | Change | | --- | --- | | `package.json` (root) | New `db:seed` npm script in `scripts` carrying the ts-node invocation. `prisma.seed` becomes `pnpm run db:seed` — the indirection forces cwd = workspace root before ts-node resolves the seed path. | That's it. Two-line diff. ## Why `pnpm run` indirection works | Step | cwd | Path resolution | | --- | --- | --- | | User invokes `pnpm exec prisma db seed` from `apps/portal-bff/` | `apps/portal-bff/` | — | | Prisma CLI walks up, finds the workspace `package.json`, reads `prisma.seed` | `apps/portal-bff/` | — | | Prisma spawns the seed command | `apps/portal-bff/` (inherited) | — | | Seed command is `pnpm run db:seed` | `apps/portal-bff/` | — | | **`pnpm run` resets cwd to the package's root (workspace root)** | **workspace root** | — | | ts-node sees `apps/portal-bff/prisma/seed.ts` | workspace root | resolves correctly ✓ | The same flow works if the user invokes from the workspace root — `pnpm run` still ends up at workspace root, idempotent. ## Test plan - [ ] **Locally**: `cd apps/portal-bff && pnpm exec prisma db seed` — should report `[seed] test-tenant complete — created … Person + … User + … UserScope rows; …`. No `MODULE_NOT_FOUND`. - [ ] **Also locally**: same command from the workspace root (`pnpm exec prisma db seed`) — same result. - [ ] **Idempotency** — re-run, expect `created 0 Person + 0 User + 0 UserScope rows`. - [x] No application code changed — drift gate, lint, tests untouched. - [ ] **Review focus** — the `pnpm run` indirection trick + the cwd table above. ## Notes for the reviewer - The deprecation warning about `package.json#prisma` (Prisma 7 migration to `prisma.config.ts`) is unchanged — not in this fix's scope. - Could have also fixed by changing the path to be relative-to-bff (`prisma/seed.ts`) and forcing the user to run from `apps/portal-bff/`. The `pnpm run` approach is cwd-invariant — more robust to where the user invokes from. - The new `db:seed` script is callable directly (`pnpm db:seed`) which is a small ergonomic bonus. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #234 |
||
|
|
9b4b4b90d0 |
feat(auth): swap stub for PrismaScopeResolver + add test-tenant seed (ADR-0026 PR 2a) (#233)
## Summary
ADR-0026 PR 2 (first half — backend). Ships:
- **`PrismaScopeResolver`** replacing `StubScopeResolver` in `AuthModule`. Queries `user_scopes WHERE userId = ? AND (expiresAt IS NULL OR expiresAt > NOW())` and maps each row to a typed `Scope` from `shared-auth`.
- **`prisma/seed.ts`** populating Person + User + UserScope rows for the 19 test-tenant personas per `notes/test-tenant-role-assignments.md`. Idempotent — re-running preserves existing rows.
- **`infra/test-tenant.personas.example.json`** — schema template for the gitignored per-persona Entra `oid` map the seed reads.
The admin UI scope-seeding screen `/admin/users/:id/scopes` is split into **PR 2b** (Angular work, follows separately).
## What lands
| File | Change |
| --- | --- |
| `apps/portal-bff/src/auth/prisma-scope-resolver.ts` + `.spec.ts` | **New** Prisma-backed resolver. Server-side `expiresAt` filter (`null OR > NOW()`). Maps `(kind, value)` rows to the discriminated `Scope` union via a pure `toScope` helper. Off-catalogue `kind` values are skipped + WARN-logged (defense in depth — the drift gate is the canonical write-side guard). 10 spec tests covering query shape, valueless / value-bearing kinds, defensive skip on off-catalogue, edge cases on `toScope`. |
| `apps/portal-bff/src/auth/auth.module.ts` | `{ provide: ScopeResolver, useClass: StubScopeResolver }` → `useClass: PrismaScopeResolver`. The `StubScopeResolver` class stays exported from `scope-resolver.ts` (handy for spec fixtures / future "force-unrestricted" dev modes) but is no longer the default wiring. |
| `apps/portal-bff/prisma/seed.ts` | **New** seed script. Hardcoded `PERSONAS` array (19 entries, slug + email + displayName + scope tuples — transcribed from `notes/test-tenant-role-assignments.md`). For each persona: reads its Entra `oid` from `TEST_TENANT_PERSONAS_PATH` (env var, default `infra/test-tenant.personas.json`); if missing → skip with WARN; otherwise idempotent upsert (findUnique by `entraOid` → reuse, or create Person + User in nested-create transaction with `Person.source = 'seed'`). Then idempotent upserts for each scope (findUnique by `(userId, kind, value)` → skip, or create with `UserScope.source = 'seed'`). Final log: counts of rows created + personas skipped. |
| `infra/test-tenant.personas.example.json` | **New** schema template — flat `{ slug: entra-oid }` map for the 19 personas + a `_README` field documenting the shape. Distinct from `test-tenant.entra.json` (the 24-entry GROUP-guid map for `EntraGroupToRoleResolver`). Real file is gitignored. |
| `apps/portal-bff/.env.example` | New `TEST_TENANT_PERSONAS_PATH` block with the same documentation pattern as `ENTRA_GROUP_MAP_PATH`. |
| `package.json` | `prisma.seed` field added: `ts-node --transpile-only --compiler-options '...' apps/portal-bff/prisma/seed.ts`. Lets `pnpm exec prisma db seed` run the script without a project-specific tsconfig dance. |
## Key choices
- **`PrismaScopeResolver` swap is the default for AuthModule.** No "fallback to stub when DB is unreachable" — a Postgres outage that prevents reading `user_scopes` should fail the sign-in (same posture as `PersonAndUserProvisioner.ensureUser`, which is also blocking). The `StubScopeResolver` class remains in `scope-resolver.ts` for spec fixtures + as a hint of how to wire a "force-everyone-unrestricted" dev override if we ever want one.
- **Defensive `toScope` mapping.** The drift gate enforces catalogue membership at the write site (the future admin scope-seeding UI from PR 2b). The Prisma read side defends in depth by skipping off-catalogue rows — a row with `kind = 'something-bogus'` (e.g. a future migration mistake) is dropped from the resolved scopes + logged, rather than throwing on every sign-in for that user. The unit test `'skips + warns on off-catalogue kinds'` pins the behaviour.
- **Seed reads Entra `oid`s from a gitignored file.** Same pattern as `EntraGroupToRoleResolver` (path via env var, file is gitignored, schema template in `infra/*.example.json`). The `oid`s themselves are tenant-private; the 19 persona slugs + their scope tuples are project-wide and live in `seed.ts`.
- **Seed is idempotent on every level.** The unique constraint on `User.entraOid` lets us "create if missing" without locks; the unique on `(userId, kind, value)` does the same for `UserScope`. Re-running the seed after a partial run picks up where it stopped — no `--reset` needed.
- **No spec for `seed.ts` itself.** It's an integration data loader; meaningful test would require a real Prisma client + DB (or a heavy mock fixture). The persona matrix is hand-verified at PR review; the seed's correctness is exercised by running it on the dev DB (test-plan checkbox below).
## Verification path
- [x] `node scripts/check-catalogue-drift.mjs` — clean (`4 / 24 / 7 / 3`).
- [x] `node --test scripts/check-catalogue-drift.spec.mjs` — 29 tests passing.
- [x] `pnpm exec nx lint portal-bff` — **0 errors**, 13 warnings (all pre-existing).
- [x] `pnpm exec nx test portal-bff` (affected specs filtered) — **774 tests passing** (was 766; +8 for `prisma-scope-resolver.spec.ts`).
- [ ] **Locally / on dev DB**:
- `cp infra/test-tenant.personas.example.json infra/test-tenant.personas.json` + fill in real `oid`s from the Entra admin centre.
- `cd apps/portal-bff && pnpm exec prisma db seed` — should report `created 19 Person + 19 User + N UserScope rows; skipped 0 personas` on a fresh DB.
- Re-run the seed → second invocation reports `0 / 0 / 0 created; skipped 0` (idempotent).
- Sign in via the user portal as one of the 19 personas → `principal.scopes` on the session contains the seeded scopes (e.g. `directeur-bordeaux` sees `[{ kind: 'etablissement', value: '0330800013' }]`).
- [ ] **Review focus** — the `toScope` mapping (especially the defensive `null` branch on off-catalogue kinds), the seed's idempotency invariants, the `prisma.seed` command in `package.json`, the comments-only fields in `test-tenant.personas.example.json`.
## What's next
**PR 2b — Admin `/admin/users/:id/scopes` screen.** Angular admin-app SPA screen + BFF read/write controllers, against the schema this PR + ADR-0026 PR 1 + ADR-0027 PR 1 have now stood up. A11y review per ADR-0016 §"Manual testing cadence". Operator workflow only at that point — the seed in this PR is the bootstrap path for the test tenant.
Once both PR 2a + PR 2b ship: **the `@RequireScope` stack is end-to-end live** for the first time. ADR-0025's stubs (`StubScopeResolver`, the entraOid placeholder on `Principal.user.{id, personId}`) are then fully retired.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #233
|
||
|
|
24071a63ec |
feat(users): add Person + User + UserScope + lazy provisioner (ADR-0026 PR 1) (#232)
## Summary
First implementation PR for [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md) (portal-side identity model). Ships:
- `Person` golden record + `User` portal-account overlay + `UserScope` Prisma models;
- `PersonAndUserProvisioner` service called from `SessionEstablisher.establish` (blocking — lazy-creates `Person` + `User` at first OIDC callback);
- `PERSON_SOURCES` closed-set catalogue + drift-gate extension;
- `PrincipalBuilder.build(user, identity)` signature update + `ScopeResolver.resolve({ userId })` seam change — `Principal.user.{id, personId}` now carry real portal UUIDs, no longer the `entraOid` placeholder.
No consumer for `UserScope` yet — `PrismaScopeResolver` lands in ADR-0026 PR 2. The `StubScopeResolver` continues to return `[{ kind: 'unrestricted' }]` for everyone.
## What lands
| File | Change |
| --- | --- |
| `apps/portal-bff/prisma/schema.prisma` | **+3 models** in the `public` schema: `Person` (UUID PK + PII + nullable email indexed-not-unique + source catalogue + nullable externalId + back-ref to User), `User` (UUID PK + 1-to-1 personId FK + unique entraOid + tenantId + lastSignInAt + scopes[]), `UserScope` (UUID PK + userId FK with `onDelete: Cascade` + kind + opaque value with `@default("")` + source + nullable expiresAt + `@@unique([userId, kind, value])`). All comments explain the cross-references to ADR-0025 / ADR-0027 / ADR-0029. |
| `apps/portal-bff/prisma/migrations/20260526210000_add_person_user_userscope/migration.sql` | **New** hand-written migration. DDL for the 3 tables, indexes (Person: source/externalId/email; User: unique personId + unique entraOid; UserScope: unique composite + plain userId), FKs with the correct `ON DELETE` actions (User.personId → RESTRICT; UserScope.userId → CASCADE — both Prisma defaults given the schema's `Delegation?` / explicit `onDelete: Cascade`). |
| `apps/portal-bff/src/users/person-source.ts` + `.spec.ts` | **New** catalogue. `PERSON_SOURCES = ['self-signin', 'admin-ui', 'seed'] as const`, `PersonSource` type union, `isPersonSource` type guard. Spec follows the structure-kind shape (catalogue content, no duplicates, type guard true/false, narrowing test via `String()` to widen). |
| `apps/portal-bff/src/users/person-and-user-provisioner.service.ts` + `.spec.ts` | **New** blocking provisioner. Fast path: `findUnique by entraOid` + `update lastSignInAt`. Cold path: nested `create` of Person + linked User in a single transaction. Race-condition handling: catches P2002 on `User.entraOid` and re-runs `ensureUser` (loser of a concurrent first-sign-in falls through to the now-warm fast path). Defense-in-depth `isPersonSource` check on the constant. Spec covers cold/warm/race/non-P2002-propagation paths + `splitDisplayName` cases (single token / trailing whitespace / empty / multi-word). |
| `scripts/check-catalogue-drift.mjs` | Property-literal scanner generalised. Adds `extractPersonSources` + `findPersonSourceViolationsInFile` (property `source` instead of `kind`, otherwise identical to the structure-kind scanner). The two extractors share `extractAsConstArray(path, constName)` and the two property scanners share `findPropertyLiteralViolations(path, validValues, sourceText, opts)`. Error-message formatter unchanged. Closing hint extended to reference all three catalogue locations. |
| `scripts/check-catalogue-drift.spec.mjs` | Fixture now writes a `person-source.ts` alongside `structure-kind.ts`. 7 new tests: extractPersonSources (2), findPersonSourceViolationsInFile (5: skip-no-import, no-violation, flag, non-literal, line/col). Aggregation test updated to assert decorator + Structure.kind + Person.source violations all surface together. **29 tests total, all passing.** |
| `apps/portal-bff/src/auth/scope-resolver.ts` | `resolve(input: { entraOid })` → `resolve(input: { userId })`. Stub still ignores its argument; PR 2's `PrismaScopeResolver` will key queries on `User.id`. Comment block updated to reflect that ADR-0026 PR 1 is now landed (no longer "proposed"). |
| `apps/portal-bff/src/auth/principal-builder.ts` | Signature: `build(user)` → `build(user, identity: { userId, personId })`. `Principal.user.id` / `Principal.user.personId` populated from `identity` instead of `user.oid`. Scope resolver called with `{ userId: identity.userId }`. Doc comment block updated to remove the "placeholder" caveat and document the new wiring. |
| `apps/portal-bff/src/auth/principal-builder.spec.ts` | New `TEST_IDENTITY` constant. Every `builder.build(...)` call gets the second arg. New assertions on `principal.user.id` / `principal.user.personId` carrying the test identity. Edge-case test "asks the scope resolver to resolve by entraOid" renamed + retargeted to `{ userId }`. **All 19 persona tests + 5 edge cases pass.** |
| `apps/portal-bff/src/auth/session-establisher.service.ts` | New constructor arg `personUserProvisioner: PersonAndUserProvisioner`. `establish()` calls `ensureUser({ oid, tenantId, displayName, email: user.username })` **before** `principalBuilder.build` so the identity is available. Comment block explains the Entra `preferred_username` → `Person.email` mapping and the blocking-vs-best-effort distinction with `UserDirectoryService.recordSignIn`. |
| `apps/portal-bff/src/auth/session-establisher.service.spec.ts` | Fixture extended with `provisioner` mock + `PROVISIONED_IDENTITY` constant. `STUB_PRINCIPAL` now carries those UUIDs instead of `user.oid`. New tests: (1) provisioner is called with the right input shape; (2) provisioner runs **before** `principalBuilder.build` (`mock.invocationCallOrder` assertion); (3) provisioner failure propagates and nothing downstream runs (build / audit / directory). |
| `apps/portal-bff/src/auth/auth.controller.spec.ts` | Provisioner mock added to the controller fixture (the controller's specs don't exercise provisioning themselves but `SessionEstablisher`'s constructor needs the arg). Principal stub's UUIDs aligned with the new provisioned shape. |
| `apps/portal-bff/src/users/users.module.ts` | `PersonAndUserProvisioner` registered + exported alongside `UserDirectoryService`. `@Global` so the auth module's `SessionEstablisher` can inject both without re-routing the module graph. Comment block updated to document the blocking/best-effort distinction between the two. |
## Key choices
- **Provisioner is blocking**, not best-effort. Distinct from `UserDirectoryService.recordSignIn` which is still best-effort (ADR-0020 admin-list cache, swallows its own errors). Both run from `SessionEstablisher.establish` per the new ordering: (1) provisioner — blocking, (2) build principal — uses identity, (3) save session, (4) cookie, (5) user-session index (best-effort), (6) audit (blocking ADR-0013), (7) UserDirectoryService.recordSignIn (best-effort), (8) log. A provisioner failure short-circuits the whole flow before any of (3)..(8) — the spec asserts this explicitly.
- **No email-based dedup in v1.** `Person.email` is indexed (not unique). The provisioner keys ONLY on `entraOid`. ADR-0026 §"Why no email-based merging in v1" — two distinct humans genuinely share emails; ADR-0029's sync flow handles operator-confirmed reconciliation.
- **Race-condition handling on first sign-in.** Two concurrent first-sign-ins for the same `entraOid` both fall through to `create`. The unique constraint on `User.entraOid` rejects the loser with P2002; the provisioner catches that specific code and re-runs `ensureUser` — fast path now warm. Pathological infinite-loop guarded by the warm-path behaviour on the second attempt. Spec covers the success retry + the non-P2002 propagation paths.
- **ScopeResolver seam moved from `{ entraOid }` to `{ userId }`.** The stub doesn't care about its argument either way; the change is the seam for ADR-0026 PR 2's `PrismaScopeResolver`, which keys `userScope` queries on `User.id` (UUID). Doing the rename now keeps PR 2 to "swap the implementation" only.
- **`PersonAndUserProvisioner.SELF_SIGNIN_SOURCE`** is a typed constant inside the class, not a magic string. Defense in depth: TS type union (compile-time) + drift gate (`source: 'self-signin'` literal is in a file importing `person-source.ts` — flagged if it ever drifts off-catalogue) + runtime `isPersonSource` check (error log + throw if the constant is mutated to an off-catalogue value).
- **UserDirectoryService stays.** ADR-0020's admin-list cache is functionally redundant with Person + User now, but folding it would extend the PR scope (DTO + reader + admin UI + migration of existing rows). Out of scope here — flagged as a follow-up.
## Local verification
- [x] `node scripts/check-catalogue-drift.mjs` — clean (`4 privileges, 24 roles, 7 structure kinds, 3 person sources`).
- [x] `node --test scripts/check-catalogue-drift.spec.mjs` — **29 tests passing** (was 22 — +7 for PERSON_SOURCES).
- [x] `pnpm exec nx lint portal-bff` — **0 errors**, 13 warnings (all pre-existing, none from this PR).
- [x] `pnpm exec nx test portal-bff` (filtered to the 6 affected spec files) — **766 tests passing**.
- [x] `pnpm exec prisma generate` — client regenerated with the 3 new models.
## Test plan — remaining (on a fresh dev DB)
- [ ] `./infra/local/dev.sh down -v && ./infra/local/dev.sh up` (wipe + reboot) then `cd apps/portal-bff && pnpm exec prisma migrate dev` — applies the new migration cleanly, no drift prompt.
- [ ] `pnpm exec prisma studio` — `persons` / `users` / `user_scopes` tables visible, all empty (no seed in this PR).
- [ ] Sign in via `apf-portal` against the test tenant — `users` table gets one row, `persons` table gets one row, `user_scopes` stays empty. Principal in the session carries the provisioned UUIDs (not the `entraOid`).
- [ ] **Review focus** — the provisioner's race-handling logic; the `Entra preferred_username → Person.email` mapping in `SessionEstablisher`; the ScopeResolver seam change rationale; the FK actions in the migration SQL (especially CASCADE on UserScope.userId vs RESTRICT on User.personId).
## What's next
- **ADR-0026 PR 2** — `PrismaScopeResolver` replacing `StubScopeResolver` + `/admin/users/:id/scopes` admin-app screen + `prisma/seed.ts` populating the 19 test personas' `user_scopes` per `notes/test-tenant-role-assignments.md` (referencing this PR's `User.id` and ADR-0027 PR 1's `Structure.code` values).
- **Future PR (out of this scope)** — fold `UserDirectoryEntry` into Person + User now that the latter exists. Touches AdminUsersReader, the DTO, the admin SPA, and a data migration for existing rows. Defer until ADR-0026 PR 2 stabilises.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #232
|
||
|
|
cba36394c9 |
fix(structures): widen via String() in narrowing test to satisfy lint (#231)
## Summary
One-line lint fix in [apps/portal-bff/src/structures/structure-kind.spec.ts](apps/portal-bff/src/structures/structure-kind.spec.ts) — the narrowing test failed CI lint with `@typescript-eslint/no-inferrable-types` on `const candidate: string = 'medico_social'`.
The naive fix (drop the annotation) would make the test vacuous: TypeScript would infer the literal type `'medico_social'`, which is already a `StructureKind` subtype, so the runtime guard `isStructureKind` would have nothing to prove. Fix instead: widen via `String('medico_social')` — the inferred return type is `string`, the narrowing check stays meaningful, the linter is happy.
## What lands
| File | Change |
| --- | --- |
| `apps/portal-bff/src/structures/structure-kind.spec.ts` | `const candidate: string = 'medico_social'` → `const candidate = String('medico_social')`. 3-line comment explains why `String()` rather than dropping the annotation. |
## Test plan
- [x] `pnpm exec nx lint portal-bff` — `0 errors, 13 warnings` (warnings all pre-existing in unrelated files).
- [x] `pnpm exec prettier --check` clean.
- [ ] **CI** — `pnpm ci:check` passes on this PR.
- [ ] **Review focus** — the comment block explaining the round-trip rationale (otherwise the `String('literal')` pattern reads like over-engineering).
## Notes for the reviewer
- **Pre-existing warnings are NOT addressed here.** The 13 remaining lint warnings (non-null assertions in `principal-extractor.spec.ts`, unused underscored params in `rate-limit.middleware.ts`, one stale eslint-disable in `downstream-token-cache.service.spec.ts`) are outside the scope of this PR — none of them block CI today, and folding them in would muddle the diff. Worth a separate `chore(bff): sweep lint warnings` PR if/when those become noisy.
- **Why `String()` over an `as string` cast?** Both would work, but `String()` is a real runtime operation (returns a fresh `string`) — `as string` is type-system-only. The runtime call has a tiny side-effect (and the inferred return type really IS `string`, not the literal), so the narrowing test stays semantically real.
- **No new error class.** The lint rule `@typescript-eslint/no-inferrable-types` is set to `error` severity in the workspace config; my narrowing test was just the first case to trip it.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #231
|
||
|
|
8d43717d25 |
fix(deps): update dependency @scalar/nestjs-api-reference to v1.1.19 (#225)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@scalar/nestjs-api-reference](https://github.com/scalar/scalar) ([source](https://github.com/scalar/scalar/tree/HEAD/integrations/nestjs)) | dependencies | patch | [`1.1.16` -> `1.1.19`](https://renovatebot.com/diffs/npm/@scalar%2fnestjs-api-reference/1.1.16/1.1.19) | --- ### Release Notes <details> <summary>scalar/scalar (@​scalar/nestjs-api-reference)</summary> ### [`v1.1.19`](https://github.com/scalar/scalar/blob/HEAD/integrations/nestjs/CHANGELOG.md#1119) ### [`v1.1.18`](https://github.com/scalar/scalar/blob/HEAD/integrations/nestjs/CHANGELOG.md#1118) ### [`v1.1.17`](https://github.com/scalar/scalar/blob/HEAD/integrations/nestjs/CHANGELOG.md#1117) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #225 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
b84b58068a |
refactor(users): rename Prisma User model to UserDirectoryEntry (#230)
## Summary
**Mechanical refactor only**, prerequisite to ADR-0026 PR 1. Renames the existing Prisma `User` model (the ADR-0020 user-directory cache, `oid` PK, written by `UserDirectoryService.recordSignIn`) to `UserDirectoryEntry`. Frees the `User` name for the upcoming ADR-0026 model (UUID PK, FK to `Person`, `lastSignInAt` — different semantics).
Zero behavioural change. Same columns, same indexes, same constraints, same call sites — just a different identifier on the model and the table.
## What lands
| File | Change |
| --- | --- |
| `apps/portal-bff/prisma/schema.prisma` | `model User` → `model UserDirectoryEntry`. `@@map("users")` → `@@map("user_directory_entries")`. Comment block extended to flag the upcoming distinction from ADR-0026's new `User`. |
| `apps/portal-bff/prisma/migrations/20260526200000_rename_users_to_user_directory_entries/migration.sql` | **New**. `ALTER TABLE "users" RENAME TO "user_directory_entries"` + `ALTER INDEX` renames for the PK constraint and the two named indexes (Postgres doesn't auto-rename these on table rename). |
| `apps/portal-bff/src/users/user-directory.service.ts` | Two renames: the **TS input interface** `UserDirectoryEntry` → `RecordSignInInput` (the existing name was a misnomer — it's the input to `recordSignIn`, not the entry itself, and would collide with the Prisma-generated `UserDirectoryEntry` type after the model rename). The **Prisma client ref** `this.prisma.user.upsert` → `this.prisma.userDirectoryEntry.upsert`. |
| `apps/portal-bff/src/users/user-directory.service.spec.ts` | Imports / mock setup / fixture type updated to track the two renames. |
| `apps/portal-bff/src/admin/admin-users-reader.service.ts` | `this.prisma.user.{count,findMany}` → `this.prisma.userDirectoryEntry.{count,findMany}`. `Prisma.UserWhereInput` → `Prisma.UserDirectoryEntryWhereInput`. Doc comments mentioning `public.users` updated to `public.user_directory_entries`. The class name `AdminUsersReader`, the endpoint URL `/api/admin/users`, the DTO `AdminUserDto`, and the local `interface UserRow` are unchanged — these are SPA/HTTP-facing identifiers, where the URL semantics ("admin user list") still hold regardless of the backing table name. |
| `apps/portal-bff/src/admin/admin-users-reader.service.spec.ts` | Mock setup updated to track the Prisma client field rename. |
## Why two renames in one file
`apps/portal-bff/src/users/user-directory.service.ts` had a TypeScript `interface UserDirectoryEntry` carrying the **input shape** of `recordSignIn(entry: UserDirectoryEntry)`. After the Prisma model rename to `UserDirectoryEntry`, that name would clash with the Prisma-generated row type. The fix is to rename the TS interface to its proper role — `RecordSignInInput` — at the same time. Net effect: clearer naming on both sides (the call-input name now describes the call, the persisted-row type name now describes the row).
## Recovery procedure (for anyone with the old migration applied locally)
The new migration `20260526200000_rename_users_to_user_directory_entries` is a pure `ALTER TABLE ... RENAME` + index renames — Prisma's `migrate dev` runs it forward without prompting:
```bash
cd apps/portal-bff && pnpm exec prisma migrate dev
# Should report: "Applied migration `20260526200000_rename_users_to_user_directory_entries`"
```
No `down -v` needed — existing data carries over.
## Test plan
- [x] `node scripts/check-catalogue-drift.mjs` — clean (4 / 24 / 7).
- [x] `node --test scripts/check-catalogue-drift.spec.mjs` — 22 tests passing.
- [x] `pnpm exec prettier --check` clean on the touched files.
- [x] Sweep grep — zero leftover `model User`, `prisma.user.`, `Prisma.UserWhereInput`, `interface UserDirectoryEntry`, or `public.users` references anywhere under `apps/portal-bff/src/` or `apps/portal-bff/prisma/`.
- [ ] **Locally**: `pnpm exec prisma migrate dev` applies the rename migration cleanly; `pnpm exec nx test portal-bff` runs the updated specs green.
- [ ] **Review focus** — the two-rename rationale in `user-directory.service.ts`, the migration's `ALTER INDEX` clauses (don't forget those — `ALTER TABLE ... RENAME` does NOT cascade to index names in Postgres), the unchanged class/URL/DTO/local-interface identifiers in `admin-users-reader.service.ts`.
## Why ship as a separate PR
ADR-0026 PR 1's nominal scope is `Person` + `User` + `UserScope` + provisioner + drift gate + PrincipalBuilder — already ~15+ files touched. Folding the rename into that PR would mix mechanical refactor with new design. Splitting keeps both PRs reviewable for what they actually do.
## What's next (post-merge)
**ADR-0026 PR 1** — `Person` + new `User` + `UserScope` schema + `PersonAndUserProvisioner` wired into `SessionEstablisher` + `Person.source` catalogue + drift-gate extension + `PrincipalBuilder` populating `Principal.user.{id, personId}` from real rows. Now unblocked.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #230
|
||
|
|
ff1713eb6d |
fix(structures): align Structure.delegation_code FK action with Prisma default (#229)
## Summary Single-line fix on the `add_org_hierarchy` migration shipped in [#228](#228) (ADR-0027 PR 1). The FK action on `Structure.delegation_code` was `ON DELETE RESTRICT` (Prisma's default for **required** relations); the field is **optional** (`Structure.delegation Delegation?`), so Prisma's default is `ON DELETE SET NULL`. Mismatch caused `prisma migrate dev` to detect drift and prompt for a corrective migration on every run. Caught locally on first VM-side validation (the workspace dev DB). No production deployment of #228 yet, so the fix is **edit-in-place** rather than a corrective sibling migration — keeps the repo's migration history clean. ## What lands | File | Change | | --- | --- | | `apps/portal-bff/prisma/migrations/20260526143000_add_org_hierarchy/migration.sql` | `ON DELETE RESTRICT` → `ON DELETE SET NULL` on the `structures_delegation_code_fkey` FK. Inline comment explains the rule (nullable Prisma relation → SET NULL is the matching default; not matching it generates drift on every `migrate dev`). | That's it. One line of SQL, one comment block. No schema.prisma change, no test change, no other file touched. ## Why edit-in-place vs new corrective migration Edit-in-place is safe here because: - The broken migration only exists in **dev local DBs** (Julien's WSL postgres). No staging, no preview env, no prod has applied it. - Every dev who pulls this PR's fix wipes their local DB (`./infra/local/dev.sh down -v`) and reapplies — clean migration history, no "modified after applied" warning. - The repo's migration list stays minimal — adding a corrective migration would carry the wart forever in `prisma/migrations/`. Once a non-dev environment has applied a migration, the rule reverses: corrective migration mandatory, never edit in place. ADR-0015's "trunk-based + squash-merge" and the absence of a deployed environment at this stage gives us this one-time window. ## Recovery procedure for anyone who applied #228 ```bash # From the workspace root ./infra/local/dev.sh down -v # wipe the local postgres volume ./infra/local/dev.sh up # Pull this fix git pull # or git switch fix/adr-0027-pr1-fk-action-on-delete depending on local state # Reapply migrations — no prompt this time cd apps/portal-bff && pnpm exec prisma migrate dev # Should report: "Applied migration `20260526143000_add_org_hierarchy`" and exit cleanly. ``` If anyone has a stray `drift_inspection/` folder under `prisma/migrations/` (artifact of the `--create-only` debugging step), delete it before reapplying — it's not in the repo, it was just diagnostic output. ## Test plan - [x] `node scripts/check-catalogue-drift.mjs` — clean (unchanged by this fix). - [x] `node --test scripts/check-catalogue-drift.spec.mjs` — 22 tests passing (unchanged). - [x] `pnpm exec prettier --check` clean (SQL file unaffected by prettier but verified). - [ ] **Locally on WSL** — wipe DB → pull fix → `prisma migrate dev` exits clean, no drift prompt. - [ ] **Review focus** — the comment block in the migration explaining the rule (future-proof against the same mistake when ADR-0026 PR 1 adds optional relations). ## Notes for the reviewer - **Why a comment block on the FK line, not on every nullable FK in future migrations?** This was caught the first time we hit it; a short note at the offending site is cheaper than amending `CLAUDE.md` with a "remember to match Prisma default actions" rule. If we hit the same mistake on ADR-0026 PR 1's `Person`/`User`/`UserScope` migration, we'll consider promoting it. For now: one inline comment at the place where the rule is non-obvious. - **The drift gate doesn't catch this kind of mistake.** Catalogue drift gate scans string literals against TypeScript catalogues — it doesn't look at SQL referential actions. Postgres CHECK constraints (introduced for `Structure.kind` in this same migration) are not the same surface as FK referential actions. Catching this required actually running `prisma migrate dev`. A possible future improvement: a CI gate that runs `prisma migrate diff --from-migrations --to-schema-datamodel --script --shadow-database-url …` and fails if the diff is non-empty (zero-drift gate). Worth an ADR amendment if it becomes a recurring class of bug. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #229 |
||
|
|
ba4cdcee7a |
feat(structures): add Region/Delegation/Structure schema + seed (ADR-0027 PR 1) (#228)
## Summary
First implementation PR for [ADR-0027](docs/decisions/0027-portal-side-organisational-hierarchy.md) (`Region` / `Delegation` / `Structure` portal-side organisational hierarchy). **Schema + seed + drift-gate extension only** — no consumer code yet (the `PrismaScopeResolver` that dereferences `Structure.code` from `UserScope.value` lives in ADR-0026 PR 2, which depends on this PR landing first).
Independent of ADR-0026 PR 1 at the schema level — both can ship in parallel; ADR-0026 PR 2 depends on both.
## What lands
| File | Change |
| --- | --- |
| `apps/portal-bff/prisma/schema.prisma` | **+3 models**. `Region` (INSEE code PK + name + delegations[]). `Delegation` (dept code PK + name + regionCode FK + structures[]). `Structure` (portal code PK + name + kind discriminator + nullable unique finess/siret/codePaie + nullable delegationCode FK). All in the `public` schema; matches the ADR-0027 schema sketch verbatim. |
| `apps/portal-bff/prisma/migrations/20260526143000_add_org_hierarchy/migration.sql` | **New** hand-written migration. DDL for the 3 tables. `CHECK ("kind" IN (...))` constraint mirroring `STRUCTURE_KINDS`. Indexes (FK columns + kind + uniques). Inline INSERT seed: Région Nouvelle-Aquitaine (75), Délégation Gironde (33), structures `0330800013` + `0330800021` (médico-social, FINESS = code) + `siege` (APF national, no delegation/finess). |
| `apps/portal-bff/src/structures/structure-kind.ts` | **New**. Closed-set catalogue: `STRUCTURE_KINDS = [medico_social, antenne, dispositif, entreprise_adaptee, mouvement, administratif, siege] as const`. `type StructureKind` derived from the union, `isStructureKind` type guard. |
| `apps/portal-bff/src/structures/structure-kind.spec.ts` | **New**. Jest spec — catalogue content, no duplicates, type guard true for catalogue values + false for typos / cascade-only values / empty, type narrowing at call site. |
| `scripts/check-catalogue-drift.mjs` | **Extend**. New `extractStructureKinds(path)` + `findStructureKindViolationsInFile(path, validKinds, sourceText?)`. Property-literal scanner: detects `kind: 'X'` in object literals, restricted to files that import from `structure-kind.ts` (cheap text pre-filter — `kind` is a common property name on unrelated objects and we'd false-positive everywhere otherwise). Integrated into `scanWorkspace`. Error-message formatter switched on callee shape (`@Foo('x')` for decorators, `kind: 'x'` for property literals). Closing hint updated to reference both ADR-0025 and ADR-0027 catalogue locations. |
| `scripts/check-catalogue-drift.spec.mjs` | **Extend**. Fixture writes a synthesised `structure-kind.ts` alongside `authorization.types.ts`. New tests: extract STRUCTURE_KINDS, throw on missing constant, skip files without the import, no-violation on catalogue values, flag off-catalogue values, skip non-literal initialisers, line/column tracking, scanWorkspace aggregation (decorator + Structure.kind together), scanWorkspace exposes the structureKinds set. **22 tests total, all passing.** |
## Defense in depth
Three layers stack for `Structure.kind`, deliberately:
1. **TypeScript type union** `StructureKind` — compile-time check at every typed assignment.
2. **Postgres `CHECK` constraint** in the migration — runtime enforcement at INSERT / UPDATE, defends raw SQL / casts / untrusted API input.
3. **`scripts/check-catalogue-drift.mjs`** — CI gate asserting every `kind: 'X'` literal in structure-context files is in the catalogue.
The `Privilege` / `FunctionalRole` catalogues from ADR-0025 only have layer 1 + layer 3 (no DB enforcement — those values aren't persisted as schema-checked columns). ADR-0027's `Structure.kind` is persisted, so layer 2 was practical to add — bulletproof against any code path that bypasses the type system.
## Seed scope (and what's deliberately NOT in it)
Just what the 19 test-tenant personas reference per `notes/test-tenant-role-assignments.md`:
- `Region` 75 Nouvelle-Aquitaine (only region the personas exercise)
- `Delegation` 33 Gironde (only delegation)
- `Structure` 0330800013 (APF Bordeaux, medico-social, FINESS = code)
- `Structure` 0330800021 (Complexe Mérignac, medico-social)
- `Structure` `siege` (APF national, kind=siege, no FK to a delegation, no FINESS)
**No** placeholder `entreprise_adaptee`, `antenne`, or `dispositif` row — those kinds are valid per the catalogue but the test tenant doesn't exercise them, and adding gold-plate seed data would be misleading ("what is this row used for?"). The full APF inventory lands with [ADR-0029](#)'s cascade sync; this seed is **superseded** (not extended) by that sync.
## Notes for the reviewer
- **Naming conflict with the existing `User` model.** The current `schema.prisma` already has a `User` model — but it's the ADR-0020 user-directory cache (Entra `oid` as PK, written by `UserDirectoryService.recordSignIn`). ADR-0026 PR 1 introduces a different `User` (UUID PK, FK to `Person`, `lastSignInAt`). **Out of scope here** — ADR-0027 PR 1 doesn't touch `User`. Flagging now so ADR-0026 PR 1 can plan the migration path (likely: rename the existing `User` to `UserDirectoryEntry` or fold it into the new Person + User pair).
- **Migration is hand-written**, matching the style of the two existing migrations (`init_audit_schema`, `users_directory`). Timestamp `20260526143000` chosen so it sorts after the existing `20260514192014_users_directory`.
- **`@@schema("public")`** required on every new model because the audit log uses `multiSchema` (per ADR-0013) — the public/audit split is configured at the datasource.
- **Drift gate error-message formatter** now switches on callee shape — decorator violations still print as `@Foo('x')`, property-literal violations print as `kind: 'x'` to match the offending code shape.
- **No `pnpm ci:check` impact expected** at the bff level beyond the new spec; `pnpm ci:catalogue-drift` continues to report clean (`catalogues: 4 privileges, 24 roles, 7 structure kinds`).
## Test plan
- [x] `node scripts/check-catalogue-drift.mjs` — clean (4 / 24 / 7).
- [x] `node --test scripts/check-catalogue-drift.spec.mjs` — 22 tests passing.
- [x] `pnpm exec prettier --check` clean on the touched files.
- [ ] **On the dev VM**: `pnpm prisma migrate dev` applies the migration cleanly against a fresh `infra/local/dev.compose.yml` postgres. `pnpm prisma studio` shows the seeded Region / Delegation / Structure rows.
- [ ] **On the dev VM**: `pnpm exec nx test portal-bff` runs the new `structure-kind.spec.ts` green.
- [ ] **Review focus** — the `Structure` model shape (kind discriminator, nullable unique columns, FK to Delegation), the inline seed values vs `notes/test-tenant-role-assignments.md`, the drift-gate property-literal scanner's restriction to files importing from `structure-kind.ts`.
## What's next
Per [ADR-0027 §"Phasing"](docs/decisions/0027-portal-side-organisational-hierarchy.md):
1. **This PR** — Region / Delegation / Structure schema + seed + drift gate. ✅
2. **ADR-0026 PR 1** — `Person` / `User` / `UserScope` schema + `PersonAndUserProvisioner` + drift gate extension for `Person.source` + updated `PrincipalBuilder`. Independent of (1), can ship in parallel. **Needs to resolve the existing-`User`-name collision.**
3. **ADR-0026 PR 2** — `PrismaScopeResolver` replacing `StubScopeResolver` + `/admin/users/:id/scopes` admin screen + `prisma/seed.ts` populating the 19 personas' `user_scopes` rows pointing at this PR's `Structure.code` values. **Depends on both (1) and (2).**
4. **ADR-0029** (future) — Pléiades + Acteurs+ + cascade syncs + facet schemas + `Pole` / `Service` / per-source enrichment extensions to this PR's hierarchy.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #228
|
||
|
|
670f6303fe |
docs(adr-0028): accept CI/CD + git hosting migration to GitLab (#227)
## Summary Promotes [ADR-0028](docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) from `proposed` to `accepted`. Shipped as `proposed` in [#226](#226); no open question left on either drivers, considered options, or the 4-phase migration sequence. Same shape as [#219](#219) (ADR-0026 + ADR-0027 acceptance). Once merged, **Phase 1** of the migration (`mirror-and-bootstrap` — repos pushed to GitLab, branch protection / MR templates / deploy keys / Renovate reconfig) is unblocked. ## What lands | File | Change | | --- | --- | | `docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md` | Frontmatter `status: proposed → accepted`. | | `docs/decisions/0015-cicd-gitea-actions.md` | **Status-update note** added at the top, right under the title — calls out that the platform choice "Gitea Actions" is superseded by ADR-0028, while the rest of ADR-0015's architectural principles (trunk-based + squash, all-gates-blocking, thin YAML, on-prem runners, signed commits, Conventional Commits) carry over unchanged. ADR-0015 stays `accepted`. | | `docs/decisions/README.md` | ADR-0028 row: `proposed → accepted`. | | `CLAUDE.md` | Roll-up bumped to `ADRs 0001 → 0028 accepted` with a one-sentence explanation that ADR-0028 supersedes only ADR-0015's platform choice. **CI/CD architecture bullet rewritten** to reflect the accepted-but-not-yet-implemented state — "Gitea Actions today, migrating to GitLab CE on `vm-gitlab` per ADR-0028 (4-phase rollout in follow-up PRs)". The architectural detail (gates list, thin YAML, signed commits) was preserved; only the platform headline and the act_runner→GitLab Runner line are touched. Also corrected: `ci:scan` (which doesn't exist as a script) → the real script names (`ci:catalogue-drift`, `ci:audit`, `ci:perf`, `ci:gzip-budgets`). | ## Notes for the reviewer - **No new Architecture bullet for ADR-0028 itself.** The decision is a *platform shift* for the existing CI/CD architecture, not a new architectural concern — so it folds into the existing ADR-0015 bullet rather than adding a sibling. - **The annotation pattern on ADR-0015** (status-update blockquote at the top) is the canonical MADR way to handle partial supersession without changing the frontmatter status. The architectural principles are still accepted; only the platform implementation moves. A future reader hitting ADR-0015 first sees the redirect immediately. - **The CLAUDE.md script-list correction** is a side-fix — `ci:scan` is not a real script name; the actual gates are `ci:check`, `ci:catalogue-drift`, `ci:audit`, `ci:commits`, `ci:perf`, `ci:gzip-budgets`. Updated in the same touch since the bullet was being rewritten anyway. - **No code changes**, so no `pnpm ci:check` impact. `pnpm exec prettier --check` clean on the four touched files. ## Test plan - [x] `pnpm exec prettier --check` — clean on the four touched files. - [x] ADR-0015 → ADR-0028 cross-reference resolves (the new blockquote link). - [x] `ADRs 0001 → 0028 accepted` matches reality (`grep '^status: ' docs/decisions/*.md` shows everything below 0029 as `accepted`). - [ ] **Review focus** — the ADR-0015 status-update note phrasing, the CLAUDE.md CI/CD bullet rewrite (especially the "carry over" wording), the roll-up sentence about partial supersession. ## What's next (post-merge) 1. **Phase 1 — `mirror-and-bootstrap`** — `git push --mirror gitlab` for `apf_portal` and the proto vendoring in `apf-ai-service`. GitLab side: groups, projects, branch protection (mirror Gitea's), MR templates, deploy keys, Renovate reconfigured for GitLab. **No `.gitlab-ci.yml` yet** — Gitea pipelines continue to gate. Ops work primarily; the only PR-shaped output is a Renovate config update on the apf-portal repo if its host detection changes. 2. **Phase 2 — `gitlab-ci-pipeline`** — `.gitlab-ci.yml` lands alongside `.gitea/workflows/ci.yml`. Both pipelines run in parallel for ~1 calendar week. GitLab Runner registered on `vm-gitlab`. 3. **Phase 3 — `cutover`** — remotes flip in CLAUDE.md, READMEs, `docs/setup/01-dev-debian-vm-setup.md` §8.3. `.gitea/workflows/` + `infra/ci-runners.compose.yml` deleted, Gitea read-only / archive. 4. **Phase 4 — `cleanup`** — stale references sweep, required signed-commits on `main` enabled. In parallel — once you've finished walking through the dev VM bootstrap — the paused **ADR-0027 PR 1** (Region / Delegation / Structure Prisma schema + inline seed) and **ADR-0026 PR 1** (Person / User / UserScope schema + provisioner) can ship on Gitea; they're decoupled from the migration and don't need to wait for it. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #227 |
||
|
|
04ee535de9 |
docs(adr-0028): propose CI/CD + git hosting migration Gitea -> GitLab (#226)
## Summary
Drafts [ADR-0028](docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) as `proposed`: migrate CI/CD + git hosting from Gitea (`git.unespace.com`) to GitLab CE self-hosted on `vm-gitlab` (`10.100.201.10`). The migration was anticipated by [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md) ("level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months") — that window opens now. **Decision-only PR** — the actual 4-phase migration ships across follow-up PRs after acceptance.
ADR-0028's number was previously a placeholder reference in ADR-0026 and ADR-0027 for the Pléiades + Acteurs+ sync ADR. **Renumbering**: that future sync ADR shifts to `ADR-0029`, and the placeholder links in ADR-0026, ADR-0027 and `CLAUDE.md` update to match — included in the same PR so the chain stays consistent.
## What lands
| File | Change |
| --- | --- |
| `docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md` | **New.** MADR 4.0.0 ADR, `proposed`. Decision = Option B (GitLab CE on `vm-gitlab`). Considered options A (status quo Gitea), C (Forgejo), D (cloud SaaS). Documents what carries over from ADR-0015 (architectural principles unchanged — thin YAML, trunk-based, all-gates-blocking, on-prem runners), what changes (host, pipeline file, runner type, scan tooling), the 4-phase migration sequence, and the signed-commits revisit. |
| `docs/decisions/0026-person-user-portal-data-model.md` | All 11 `ADR-0028` references → `ADR-0029` (sync + facets shifts to 0029). |
| `docs/decisions/0027-portal-side-organisational-hierarchy.md` | All 14 `ADR-0028` references → `ADR-0029`. |
| `docs/decisions/README.md` | New row for ADR-0028 (`proposed`, tags `infrastructure`, `process`, 2026-05-26). |
| `CLAUDE.md` | Roll-up updated: `ADRs 0001 → 0027 accepted; ADR-0028 + ADR-0029 proposed`. ADR-0028's relationship to ADR-0015 spelled out inline ("supersedes ADR-0015's Gitea Actions platform choice — the rest of ADR-0015's architectural principles carry over unchanged"). ADR-0026 + ADR-0027 architecture bullets renumbered 0028 → 0029 to track. |
## Key choices in the ADR
- **What carries over from ADR-0015 vs what changes** — explicit table so future readers see immediately that the migration is **platform-only**, not a re-litigation of CI principles. Trunk-based + squash, all-gates-blocking, thin YAML over portable scripts (`pnpm ci:check` etc. — unchanged), on-prem runners, Conventional Commits in CI + hook (defense in depth) — all carried over. Host, pipeline-file grammar, runner type, and scan tooling are the only things that move.
- **Native security scanning replaces the manual Trivy + gitleaks setup.** GitLab CE's built-in `Dependency-Scanning.gitlab-ci.yml` + `Secret-Detection.gitlab-ci.yml` includes consolidate the ~30 lines of inline `curl + tar` install dance currently in `.gitea/workflows/ci.yml`. Same blocking thresholds (CRITICAL+HIGH dependency vulns, any secret).
- **`vm-gitlab` is already provisioned.** No infra wait — the only sequencing constraint is operator-driven, not infrastructure-driven.
- **4-phase migration, parallel pipelines for ~1 week before cutover.** Phase 1 mirrors repos and bootstraps GitLab side (no `.gitlab-ci.yml` yet — Gitea still gates). Phase 2 lands `.gitlab-ci.yml` alongside `.gitea/workflows/ci.yml` so both pipelines run per PR until parity is confirmed. Phase 3 flips the remote URLs and deletes `.gitea/workflows/` + `infra/ci-runners.compose.yml`. Phase 4 sweeps stale references.
- **Gitea moves to read-only / archive, not decommissioned** at cutover. Existing references to Gitea PRs (`#213`, `#217`, `#219`, …) in commit messages and ADR bodies stay resolvable as historical artefacts. One VM at idle is a low long-term cost.
- **Signed commits revisit.** ADR-0015 noted "signed commits recommended, revisited at GitLab migration". ADR-0028 makes the recommendation: enable required signed commits on `main` once GitLab is live, paired with GnuPG agent forwarding (already documented in `docs/setup/01-dev-debian-vm-setup.md` §8.5). `apf-portal-bot` (Renovate) gets a dedicated signing key at PR 1.
## Renumbering — what moved and why
Before this PR, ADR-0026 and ADR-0027 used `ADR-0028` as a placeholder link for the Pléiades + Acteurs+ sync ADR. That sync ADR hasn't been drafted yet — the number was reserved.
This PR claims `ADR-0028` for the GitLab migration (the immediately-actionable decision), and shifts the sync placeholder to **ADR-0029**. All 25 link references across ADR-0026 (11) and ADR-0027 (14) update in lockstep — replace_all is safe here because in those files `ADR-0028` consistently meant "the sync ADR".
Content of the sync ADR is unchanged — only the number. When that ADR is eventually drafted as `0029-…md`, it gets the existing content reserved for it in the placeholder text.
## Test plan
- [x] `pnpm exec prettier --check` clean on the touched files.
- [x] All `ADR-0028` references in `0026-…md` / `0027-…md` / `CLAUDE.md` now read `ADR-0029` (grep confirms zero remaining references to the old number in those files).
- [x] The new `0028-…md` self-references (status frontmatter, title, internal anchors) are consistent — no leftover `0029`.
- [ ] **Review focus** — drivers / consequences / migration sequence in the ADR; the "what carries over from ADR-0015" table; the renumbering rationale.
## What's next
Per ADR-0028 §"Migration sequence", post-acceptance:
1. **ADR-0028 acceptance PR** — small status-flip, same pattern as #219 (ADR-0026 + ADR-0027 acceptance).
2. **`mirror-and-bootstrap` PR** — `git push --mirror` Gitea → GitLab; GitLab side groups / projects / branch protection / MR templates / deploy keys / Renovate reconfig. No `.gitlab-ci.yml` yet, Gitea pipelines still gate.
3. **`gitlab-ci-pipeline` PR** — `.gitlab-ci.yml` alongside the existing `.gitea/workflows/ci.yml`. Parallel runs ~1 week for parity. GitLab Runner registered on `vm-gitlab`.
4. **`cutover` PR** — remotes flip across docs, `.gitea/workflows/` + `infra/ci-runners.compose.yml` deleted, Gitea read-only.
5. **`cleanup` PR** — stale references sweep, signed-commit policy finalised on `main`.
In parallel — once the dev VM (#220 / #221 / #222 / #223 / #224) is fully bootstrapped — the paused **ADR-0027 PR 1** (Region / Delegation / Structure Prisma schema + inline seed) and **ADR-0026 PR 1** (Person / User / UserScope schema + provisioner) can ship on Gitea; they have no dependency on the GitLab migration and don't need to wait.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #226
|
||
|
|
72e1182308 |
docs(setup): version aliases.zsh + gitconfig.txt under docs/setup/dotfiles (#224)
## Summary Follow-up on [#220](#220). [`80-dotfiles.sh`](docs/setup/scripts/80-dotfiles.sh) was reading `notes/aliases.zsh` and `notes/gitconfig.txt` — but `notes/` is the project lead's personal scratchpad and is gitignored, so a fresh clone on the dev VM has nothing to read: ``` ✗ /home/APF/dev-jugautier/Works/apf_portal/notes/aliases.zsh not found. ``` Move both templates to a versioned location and update the script + docs to match. ## What lands | File | Change | | --- | --- | | `docs/setup/dotfiles/aliases.zsh` | **New.** Project-wide zsh aliases (navigation / files / search / git / dev), previously living in `notes/aliases.zsh`. Header explains scope (project-wide, all devs pick it up on next zsh restart) and where per-dev customizations go (private dotfiles repo). | | `docs/setup/dotfiles/gitconfig.txt` | **New.** Base `~/.gitconfig` template — init / core / aliases / colour. `[user]` block carries placeholder identity (`name = your name` / `email = your.email@example.com`) that the script overwrites at install via `git config --global user.{name,email}`. | | `docs/setup/scripts/80-dotfiles.sh` | Source paths flipped from `$REPO_ROOT/notes/…` to `$REPO_ROOT/docs/setup/dotfiles/…`. Header doc-comment updated. | | `docs/setup/01-dev-debian-vm-setup.md` | Step-3 table row and §8.4 (dotfiles repo) reference the new path. The §8.4 wording is also tightened — the script no longer "falls back" anywhere, it has one source of truth. | | `docs/setup/README.md` | New `dotfiles/` section in the folder index. Step-7 row in the scripts table also reflects the new paths. | ## Why this lives in `docs/setup/dotfiles/` and not `infra/` or a top-level `dotfiles/` - The directory is **consumed exclusively by [`80-dotfiles.sh`](docs/setup/scripts/80-dotfiles.sh)** — co-locating it under `docs/setup/` keeps the setup family self-contained (one folder to read, one folder to clone). - The name `dotfiles/` reads correctly for a future per-dev dotfiles repo migration (§8.4) — the templates here become the seed of that repo, and the install script will then check `~/.dotfiles/` first and fall back to `docs/setup/dotfiles/` second. ## Unblock path (if you're stuck mid-bootstrap) Either pull this PR, or manually create the two files at the **new** location on the VM: ```bash mkdir -p ~/Works/apf_portal/docs/setup/dotfiles # paste the aliases.zsh + gitconfig.txt content there ./docs/setup/scripts/80-dotfiles.sh ``` ## Test plan - [ ] On a fresh Trixie VM, `./docs/setup/scripts/80-dotfiles.sh` succeeds: aliases.zsh symlink lands at `~/.oh-my-zsh/custom/aliases.zsh` pointing at `docs/setup/dotfiles/aliases.zsh`, `~/.gitconfig` written with the prompted identity. - [ ] Re-running the script reports `↪ skip aliases.zsh symlink already correct` (idempotency). - [ ] `git config --global user.name` returns the prompted value (i.e. placeholder `your name` is overwritten). - [x] `pnpm exec prettier --check` clean on the touched files. - [x] `bash -n docs/setup/scripts/80-dotfiles.sh` clean. ## Notes - `notes/aliases.zsh` and `notes/gitconfig.txt` on existing dev workstations are unaffected — those files live outside the repo (gitignored), nothing here touches them. They can be deleted or kept as personal scratch at the dev's discretion. - A future PR creates the **private dotfiles repo** (`apf/dotfiles`) and teaches `80-dotfiles.sh` to prefer `~/.dotfiles/` over `docs/setup/dotfiles/` when both exist. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #224 |
||
|
|
1d11db9f36 |
docs(setup): drop deprecated apt packages on Debian 13 trixie (#223)
## Summary Follow-up on [#220](#220). [`10-base-packages.sh`](docs/setup/scripts/10-base-packages.sh) fails on a fresh Debian 13 Trixie VM with `E: Impossible de trouver le paquet software-properties-common`. The package is no longer shipped on Trixie — and it was never used by any of our downstream scripts in the first place. Drop three deprecated / unused packages from the install list. Add an inline comment explaining each kept package's purpose so a drive-by addition doesn't reintroduce the dropped ones. ## What lands | File | Change | | --- | --- | | `docs/setup/scripts/10-base-packages.sh` | Drop `software-properties-common`, `apt-transport-https`, `lsb-release`. Remaining minimal set: `curl wget git ca-certificates gnupg build-essential pkg-config`. Inline comment lists which downstream script consumes each kept package + which three were deliberately removed and why. | | `docs/setup/01-dev-debian-vm-setup.md` | Step-3 table row for `10-base-packages.sh` updated to match the new package list. | ## Why each removed package wasn't needed | Package | Why removed | | --- | --- | | `software-properties-common` | Drops `add-apt-repository`. We don't use it — [`50-docker.sh`](docs/setup/scripts/50-docker.sh) writes `/etc/apt/sources.list.d/docker.list` manually with the keyring-pinned `signed-by=` form. Also no longer in Trixie. | | `apt-transport-https` | Transitional package since apt 1.5 (2018) — apt has native HTTPS. Intermittently absent on Trixie. | | `lsb-release` | Shell scripts read `/etc/os-release` directly (see `50-docker.sh`'s `. /etc/os-release && echo "${VERSION_CODENAME}"`). | ## Unblock path (manual, if you're stuck mid-bootstrap) The user can either pull this PR and re-run, or shortcut manually: ```bash sudo apt-get install -y curl wget git ca-certificates gnupg build-essential pkg-config ./docs/setup/scripts/20-zsh.sh # continue from the next step ``` Scripts are idempotent — re-running `bootstrap.sh` after pulling this PR is also safe. ## Test plan - [ ] On a fresh Trixie VM, `./docs/setup/scripts/10-base-packages.sh` exits successfully. - [ ] Verify each downstream script still has the binaries it expects (curl in 40-node.sh, gpg in 50-docker.sh, build-essential in pnpm install, …). - [x] `pnpm exec prettier --check` clean on the touched files. - [x] `bash -n docs/setup/scripts/10-base-packages.sh` clean. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #223 |
||
|
|
d99254a280 |
docs(setup): make fail2ban opt-in in 70-hardening.sh (#222)
## Summary Follow-up on [#220](#220) / [#221](#221). Makes fail2ban **opt-in** in [`70-hardening.sh`](docs/setup/scripts/70-hardening.sh) instead of installing it unconditionally. Reasoning: some corp environments already ship brute-force protection at the network layer (ACL / corp firewall / appliance) — fail2ban on the host then becomes redundant and can be the wrong layer to debug from when a rule misfires. The other three hardening steps (UFW enable, sshd lockdown) were already prompt-gated; fail2ban was the odd one out. ## What lands | File | Change | | --- | --- | | `docs/setup/scripts/70-hardening.sh` | fail2ban block restructured into three branches: (1) already running → skip; (2) installed but stopped → prompt to enable+start; (3) not installed → prompt to install+enable+start. Each "no" path logs `↪ skip (user choice)` so re-runs don't repeatedly nag if the dev has already declined. | | `docs/setup/01-dev-debian-vm-setup.md` | Table row for `70-hardening.sh` clarified — each sub-step's prompt posture is now visible: UFW prompts before enabling, fail2ban prompts before installing, sshd hardening prompts before applying. `unattended-upgrades` is the only one applied unconditionally. | | `docs/setup/README.md` | Same descriptor adjustment. | ## Test plan - [ ] On a fresh Debian VM with no fail2ban installed, run `70-hardening.sh`, decline the fail2ban prompt → script continues, fail2ban not installed, no service started. - [ ] On the same VM, re-run `70-hardening.sh` → the fail2ban branch prompts again (the dev may have changed their mind); declining again produces the same `↪ skip (user choice)` result. - [ ] On a VM where fail2ban is pre-installed but stopped (rare, but possible if infra rolled it back), the script offers to start it without re-installing. - [x] `pnpm exec prettier --check` clean on the touched files. - [x] `bash -n docs/setup/scripts/70-hardening.sh` (syntax check) clean. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #222 |
||
|
|
c77313c693 |
docs(setup): use ~/.bashrc hand-off instead of chsh for zsh switch (#221)
## Summary Follow-up fix on [#220](#220). The corp infra locks the default shell at user-provisioning time on the dev VM (`10.100.201.21`) — `chsh` is denied at the PAM level, so the `sudo chsh -s` block in [`20-zsh.sh`](docs/setup/scripts/20-zsh.sh) fails on every fresh VM. Switch to the `~/.bashrc` exec-zsh hand-off — the same proven pattern used in the legacy [`02-wsl-terminal-setup.md`](docs/setup/02-wsl-terminal-setup.md). UX-identical for the dev, zero infra escalation required. ## What lands | File | Change | | --- | --- | | `docs/setup/scripts/20-zsh.sh` | Drop the `sudo chsh -s` block. Append a guarded `exec zsh -l` block to `~/.bashrc` instead, marked with a managed-by comment for idempotency on re-runs. | | `docs/setup/01-dev-debian-vm-setup.md` | Table row for `20-zsh.sh` updated — "set as default shell" → "`~/.bashrc` (exec zsh on interactive shells — `chsh` is blocked on the corp VM)". | | `docs/setup/README.md` | Same descriptor adjustment. | ## The hand-off block ```bash # Managed by docs/setup/scripts/20-zsh.sh — apf-portal zsh hand-off. # Hand off to zsh on interactive shells. The `case $-` guard avoids # breaking non-interactive bash (scp, rsync, cron, …), and the # ZSH_VERSION check prevents an infinite re-exec loop. case $- in *i*) if command -v zsh >/dev/null 2>&1 && [ -z "${ZSH_VERSION-}" ]; then exec zsh -l fi ;; esac ``` Two safety nets in the block: - **`case $- in *i*)`** — only runs the hand-off when the shell flag set contains `i` (interactive). `scp` / `rsync` / non-interactive ssh executions go through bash and are not hijacked. - **`[ -z "${ZSH_VERSION-}" ]`** — `ZSH_VERSION` is set by zsh itself; if it's already set we're already in zsh and a re-exec would loop. ## Test plan - [ ] On the dev VM, run `20-zsh.sh` on a fresh state: bash, no existing `~/.bashrc` zsh hand-off → script exits without `chsh` error, `~/.bashrc` gets the block appended, `exit` + reconnect lands in zsh. - [ ] Re-run `20-zsh.sh` → reports `↪ skip zsh hand-off already in ~/.bashrc` (idempotency). - [ ] `scp some-file vm-dev:/tmp/` from the workstation still works (non-interactive bash not hijacked). - [x] `pnpm exec prettier --check` clean on the touched markdown files. - [x] `bash -n docs/setup/scripts/20-zsh.sh` (syntax check) clean. ## Why not amend #220 #220 has already merged. Squash-merge collapsed it to `8a04540` on main; a force-push to amend would rewrite that commit and break anyone who's pulled it. The fix lands as a separate commit on the same setup-doc family. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #221 |
||
|
|
8a04540410 |
docs(setup): add Debian 13 dev-VM setup procedure + scripts + devcontainer (#220)
## Summary Adds a full Debian 13 dev-VM setup procedure ([docs/setup/01-dev-debian-vm-setup.md](docs/setup/01-dev-debian-vm-setup.md)) + 10 modular idempotent setup scripts + a systemd template + a `.devcontainer/` spec, in preparation for the new dev VM (`10.100.201.21`) replacing the WSL-based workflow. Both IDE flows (VSCode Remote-SSH + Devcontainer) and both Node toolchains (nvm on host + devcontainer image) are available — devs pick per task. Adjacent context (does not ship here, planned follow-up): - **Preview infra on the GitLab VM (`10.100.201.10`)** — same `dev.compose.yml`, deployed by CI on `main`. Doc placeholder in §8.6. - **GitLab Runner migration** (act_runner Gitea → GitLab Runner Docker executor) — bundled with the Gitea → GitLab cutover. - **Private dotfiles repo** (`apf/dotfiles`) — `~/.zshrc`, `~/.p10k.zsh`, `~/.tmux.conf` versioned. `80-dotfiles.sh` is already structured to fall back to a `~/.dotfiles/` clone when present. No application-code changes. No CI gate impact (doc + scripts + devcontainer spec only). ## What lands | Path | Change | | --- | --- | | `docs/setup/01-dev-debian-vm-setup.md` | **New.** Step-by-step doc: workstation prep (SSH agent + VSCode Remote-SSH + fonts), bootstrap orchestrator, per-script effects, project clone, infra boot, IDE flow A / B / C, apf-ai-service .NET appendix, troubleshooting. | | `docs/setup/02-wsl-terminal-setup.md` | Renamed from `01-wsl-terminal-setup.md`. No content change. | | `docs/setup/03-dev-web-stack.md` | Renamed from `02-dev-web-stack.md`. No content change. | | `docs/setup/04-angular-nx-monorepo.md` | Renamed from `03-angular-nx-monorepo.md`. No content change. | | `docs/setup/README.md` | **New.** Index of the `docs/setup/` folder. | | `docs/setup/scripts/lib.sh` | **New.** Shared helpers — colour-coded log/ok/warn/err/skip, `apt_install` skipping already-installed, `ensure_line` idempotent append, `confirm` prompt. | | `docs/setup/scripts/bootstrap.sh` | **New.** Orchestrator running scripts 10..80 in order with confirmation prompts. | | `docs/setup/scripts/10-base-packages.sh` | **New.** apt update + base packages (curl, wget, git, build-essential, …). | | `docs/setup/scripts/20-zsh.sh` | **New.** zsh + Oh My Zsh (RUNZSH=no, no shell hijack) + Powerlevel10k + `zsh-autosuggestions` + `zsh-syntax-highlighting`. Patches `~/.zshrc` (theme, plugins, fzf hook). | | `docs/setup/scripts/30-cli-tools.sh` | **New.** `bat eza fd-find ripgrep fzf zoxide ncdu keychain` + `jq yq httpie make tree htop tmux direnv dnsutils unzip rsync`. Symlinks Debian-renamed binaries (`batcat`→`bat`, `fdfind`→`fd`) into `~/.local/bin` so notes/aliases.zsh works as-is. | | `docs/setup/scripts/40-node.sh` | **New.** nvm v0.40.1 + Node from `.nvmrc` (currently 24) + corepack enable + pnpm warmed from `package.json#packageManager`. | | `docs/setup/scripts/50-docker.sh` | **New.** Docker CE + compose plugin from docker.com apt repo, user added to `docker` group, `docker.service` enabled at boot. Pinned GPG key + repo line for Debian Trixie. | | `docs/setup/scripts/60-tuning.sh` | **New.** `fs.inotify.max_user_watches=524288` (Vite/Nx watch ceiling), optional 4 GB swapfile, optional hostname rename (only prompts on generic hostnames like `debian13`). | | `docs/setup/scripts/70-hardening.sh` | **New.** Best-effort UFW (`allow OpenSSH` only) + unattended-upgrades on security channel + fail2ban + sshd drop-in (`PermitRootLogin no`, `PasswordAuthentication no`, `AllowAgentForwarding yes`). Probes before applying, validates `sshd -t` before reloading, skips cleanly if infra already locked the box down. | | `docs/setup/scripts/80-dotfiles.sh` | **New.** Symlinks `notes/aliases.zsh` → `~/.oh-my-zsh/custom/aliases.zsh` (backs up an existing target). Copies `notes/gitconfig.txt` to `~/.gitconfig`, prompts for identity, applies via `git config --global user.name/email`. | | `docs/setup/systemd/apf-portal-infra@.service` | **New.** Template systemd unit auto-starting `./infra/local/dev.sh up` at boot. Install: `enable apf-portal-infra@$USER.service`. | | `.devcontainer/devcontainer.json` | **New.** VSCode Dev Container spec on `mcr.microsoft.com/devcontainers/typescript-node:1-24-bookworm`. `docker-outside-of-docker` feature + `--network=apf-portal-dev` so DNS to `postgres`/`redis`/`otel-collector` works. `initializeCommand` fails fast if `dev.sh up` hasn't been run yet. Forwarded ports labelled. Six dev extensions pre-installed. | | `.devcontainer/post-create.sh` | **New.** `corepack enable && pnpm install --frozen-lockfile`. | | `CLAUDE.md` | "Environment conventions" rewritten: documents the two envs (`local` / `development`) + hybrid sub-mode + the two IDE flows (Remote-SSH / Devcontainer), points at the new VM setup doc and the legacy WSL doc. | ## Key choices - **Idempotent scripts, individually runnable.** Each script probes before doing anything (apt package already installed? plugin already cloned? UFW already active? sshd drop-in already present?). Bootstrap is the orchestrator; each script also runs standalone. Re-running after a partial setup is safe and reports `↪ skip` for the no-op cases. - **No private key on the VM — SSH agent forwarding instead.** `~/.ssh/config` on the workstation carries `ForwardAgent yes`; the VM never holds long-lived secrets. Same future pattern for GPG signing (covered in §8.5 as appendix). `keychain` is installed by `30-cli-tools.sh` as a fallback for scenarios where agent forwarding is not available (CI runners, scripts). - **Hardening is "best-effort, probe-first".** Some infra teams ship pre-hardened VMs; this script doesn't fight that. UFW already active? Print rules and skip. unattended-upgrades already on? Skip. SSH already locked down? Skip. Each section validates before reloading so a misconfig can't take SSH offline. - **Devcontainer assumes infra-on-host.** The container runs on the VM but talks to postgres / redis / otel **on the same VM's host docker daemon** through the shared `apf-portal-dev` Compose network. `initializeCommand` fails fast with a clear message if `./infra/local/dev.sh up` hasn't been run yet — better than puzzling `ECONNREFUSED` errors at runtime. - **`60-tuning.sh` raises inotify to 524288.** Default Debian limit is 8K; Vite/Nx in this monorepo blow past that. The setting is persisted in `/etc/sysctl.d/99-apf-portal.conf` so it survives reboot. - **Hybrid mode (workstation IDE + VM infra) is a documented sub-mode.** SSH `LocalForward` directives on 5432/6379/4317/4318 expose the VM's infra services as `localhost:*` on the workstation. Latency cost: 5-15 ms per query, fine for daily work; for long-running flows, wrap in `tmux` or use `autossh`. ## Notes for the reviewer - **Renaming the existing setup docs (01→02, 02→03, 03→04) is the only "destructive" change.** `git log --follow` still works because of `git mv`. Diff shows up as renames, not delete-and-add. - **The brief asked for `bat eza fd-find ripgrep fzf zoxide docker keychain ncdu git`.** `git` is installed by `10-base-packages.sh` (every other script needs it). Everything else lives in `30-cli-tools.sh` + `50-docker.sh`. The "fullstack-dev extras" (`jq yq httpie make tree htop tmux direnv dnsutils unzip rsync`) are additions I proposed in the lock-in question and that you greenlit (full scope) — easy to trim if any of them turn out to be unwanted. - **Both Node toolchains in parallel** — nvm on the VM (via `40-node.sh`) **and** devcontainer in the repo. Devs can use either; both read the same `.nvmrc` + `packageManager` pin so the version stays consistent. - **The systemd unit is a TEMPLATE** (`apf-portal-infra@.service`) — install once, enable per-user (`enable apf-portal-infra@$USER.service`). This is the right shape for a shared VM with multiple devs eventually, even if today only one user uses it. - **No PR-body Co-Authored-By trailer, no Generated-with-Claude footer**, per the project rule. ## Test plan Manual (no automated test exists for this kind of setup work): - [ ] On a fresh Debian 13 VM: `git clone …`, `./docs/setup/scripts/bootstrap.sh`, answer prompts, end up with zsh + Powerlevel10k + all the requested CLI tools + Node 24 + pnpm 10.33.4 + Docker on PATH. - [ ] `./infra/local/dev.sh up` boots successfully against the VM's local docker daemon. - [ ] `pnpm install` + `pnpm exec nx run-many -t lint test --parallel=3` passes on the VM. - [ ] VSCode Remote-SSH from a workstation: connect, open `~/Works/apf_portal`, run `pnpm exec nx serve portal-bff`, confirm reaches `postgres:5432`. - [ ] VSCode Dev Containers from the same workstation: `Reopen in Container`, image builds, `postCreateCommand` runs `pnpm install`, dev server reaches postgres through the `apf-portal-dev` network. - [ ] Hybrid mode: SSH tunnel from workstation, `pnpm exec nx serve portal-bff` locally, confirm postgres reachable via `localhost:5432`. - [x] `pnpm exec prettier --check` clean on the touched markdown files. - [x] Scripts pass `bash -n` (syntax check) — verified during writing. ## What's next - Validate by walking through this doc on the actual VM `10.100.201.21`. Any friction surfaced becomes a follow-up PR (`docs(setup): ...`). - Once the dev VM is operational, return to **ADR-0027 Implementation PR 1** (Region / Delegation / Structure Prisma schema + inline reference-data migration) — paused since the start of this PR. - Set up the **private dotfiles repo** (`apf/dotfiles`) as a small follow-up, then teach `80-dotfiles.sh` to prefer the dotfiles repo over `notes/`. - When migrating to GitLab: PR pair — (a) `git remote set-url` doc updates here, (b) `infra/gitlab-runners/` replacing `infra/ci-runners.compose.yml`. - Future: deploy the **shared preview infra** on `vm-gitlab` (10.100.201.10) — CI-driven, separate PR. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #220 |
||
|
|
0d8b3712fb |
docs(adr): accept ADR-0026 + ADR-0027 (#219)
## Summary Promotes [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md) and [ADR-0027](docs/decisions/0027-portal-side-organisational-hierarchy.md) from `proposed` to `accepted`. Both shipped as `proposed` in #217 after the cascade / acteurs_plus source-of-truth audit reshaped the org-hierarchy model. No open questions left on either. No code changes — same shape as #205 (ADR-0025 acceptance). ## What lands | File | Change | | --- | --- | | `docs/decisions/0026-person-user-portal-data-model.md` | Frontmatter `status: proposed → accepted`. | | `docs/decisions/0027-portal-side-organisational-hierarchy.md` | Frontmatter `status: proposed → accepted`. | | `docs/decisions/README.md` | Rows for 0026 and 0027 flip to `accepted`. | | `CLAUDE.md` | Roll-up bumped to `0001 → 0027 accepted`; two new Architecture bullets ("Portal-side identity model" and "Portal-side organisational hierarchy") added after the ADR-0025 bullet; ADR-0025's bullet adjusted to clarify the scope literal `etablissement:<structure-code>` (with a pointer to ADR-0027 for the `Structure.code` semantics); the `@RequireScope` Prisma-resolver roadmap entry now references both ADRs as accepted with the two-schema-then-resolver phasing. | ## Notes for the reviewer - **ADR-0025's bullet got a small touch-up, not a rewrite.** The original copy listed scope kinds as `etablissement:<finess>`, which was accurate when ADR-0025 shipped but is now superseded by ADR-0027's `Structure.code` semantics (FINESS for medico-social rows, internal slugs otherwise). The change is `<finess>` → `<structure-code>` + a `see ADR-0027` parenthetical. No actual decision in ADR-0025 changes. - **Two new Architecture bullets, mirrored on the ADR-0024 / ADR-0025 pair of bullets that precede them in tone + length.** The "Portal-side identity model" bullet calls out the `entraOid`-only v1 dedup and the non-unique `Person.email` (both decisions made during the split rework); the "Portal-side organisational hierarchy" bullet calls out the `Structure.code` round-trip semantics and the deferred-to-ADR-0028 items (`Pole`, `Service`, arbitrary nesting, per-source enrichment, full cascade sync). - **No code changes**, so no `pnpm ci:check` impact. `pnpm exec prettier --check` clean on the four touched files. ## Test plan - [x] `pnpm exec prettier --check` — clean on the four touched files. - [x] Internal links resolve (ADR-0026 ↔ ADR-0027 mutual references, plus the `ADR-0028` dangling marker left intentional for the future sync ADR). - [ ] **Review focus** — the two new Architecture bullets phrasing; the ADR-0025 bullet's small scope-literal touch-up; CLAUDE.md roll-up wording. ## What's next Now unblocked — per ADR-0026 §"Phasing" and ADR-0027 §"Phasing": 1. **ADR-0027 Implementation PR 1** — `Region` / `Delegation` / `Structure` Prisma schema + inline reference-data migration (Région Nouvelle-Aquitaine + Délégation 33 + a handful of test-tenant structures) + `Structure.kind` catalogue + drift-gate extension. Independent of (2) at the schema level. 2. **ADR-0026 Implementation PR 1** — `Person` / `User` / `UserScope` Prisma schema + `PersonAndUserProvisioner` called from `SessionEstablisher` + `Person.source` catalogue + drift-gate extension + updated `PrincipalBuilder` populating `Principal.user.{id, personId}` from the real rows. Can ship in parallel with (1). 3. **ADR-0026 Implementation PR 2** — `PrismaScopeResolver` replacing `StubScopeResolver` + `/admin/users/:id/scopes` admin-app screen + `prisma/seed.ts` populating the 19 test personas' `user_scopes` per `notes/test-tenant-role-assignments.md`. Depends on both (1) and (2). 4. **ADR-0028 (proposed)** — Pléiades + Acteurs+ + cascade syncs + facet schemas (Salarié / Élu / Adhérent / Bénévole / Bénéficiaire / PartenaireExterne) + operator-confirmed Person-reconciliation flow + the deferred org-hierarchy extensions (`Pole`, `Service`, per-source enrichment). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #219 |
||
|
|
7d89591f9a |
fix(deps): update dependency @grpc/grpc-js to v1.14.4 (#218)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@grpc/grpc-js](https://grpc.io/) ([source](https://github.com/grpc/grpc-node)) | dependencies | patch | [`1.14.3` -> `1.14.4`](https://renovatebot.com/diffs/npm/@grpc%2fgrpc-js/1.14.3/1.14.4) | --- ### Release Notes <details> <summary>grpc/grpc-node (@​grpc/grpc-js)</summary> ### [`v1.14.4`](https://github.com/grpc/grpc-node/releases/tag/%40grpc/grpc-js%401.14.4): @​grpc/grpc-js 1.14.4 [Compare Source](https://github.com/grpc/grpc-node/compare/@grpc/grpc-js@1.14.3...@grpc/grpc-js@1.14.4) - Fix a bug that could cause servers to crash when handling malformed requests ([advisory GHSA-5375-pq7m-f5r2](https://github.com/grpc/grpc-node/security/advisories/GHSA-5375-pq7m-f5r2)) - Fix a bug that could cause clients and servers to crash when handling malformed compressed messages ([advisory GHSA-99f4-grh7-6pcq](https://github.com/grpc/grpc-node/security/advisories/GHSA-99f4-grh7-6pcq)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #218 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
30cefc4488 |
docs(adr): split ADR-0026 + propose ADR-0027 (Structure hierarchy) (#217)
## Summary
Splits [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md) into two sibling ADRs after a cascade / acteurs_plus source-of-truth audit caught a design break: the first draft pinned `Etablissement.finess` as primary key, but ≥ 30 % of APF's real structure inventory has no FINESS (antennes, dispositifs, entreprises adaptées, mouvement, administratif, siège).
| ADR | Status | Scope |
| --- | --- | --- |
| [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md) — narrowed | `proposed` | `Person` + `User` + `UserScope` only (identity model) |
| [ADR-0027](docs/decisions/0027-portal-side-organisational-hierarchy.md) — new | `proposed` | `Region` + `Delegation` + `Structure` (cascade-aligned: `kind` discriminator + nullable FINESS / SIRET / `codePaie`) |
| `ADR-0028` (future) | — | Pléiades + Acteurs+ + cascade syncs + facet schemas (renumbered from the old `ADR-0027` placeholder) |
No code changes — all three artefacts moving in this PR are markdown.
## What lands
| File | Change |
| --- | --- |
| `docs/decisions/0026-person-user-portal-data-model.md` | Title trimmed (drop `+ organisational hierarchy`); `Region` / `Delegation` / `Etablissement` schema removed; `Person.email` unique constraint dropped (two distinct humans can share an email — see Lifecycle); Lifecycle rewritten without email-based dedup; `UserScope.value` documented as opaque string referencing ADR-0027 codes; Confirmation drops `Etablissement.kind` bullet; ADR-0027 sync references renumbered to ADR-0028; "What ADR-0026 ships vs adjacent ADRs" rewritten to three columns. |
| `docs/decisions/0027-portal-side-organisational-hierarchy.md` | **New.** Decision = Option B (`Structure` with `kind` discriminator + nullable FINESS / SIRET / `codePaie`, internal `code` PK that doubles as FINESS for medico-social structures). Considered options A (FINESS-only — original ADR-0026 draft), B (chosen), C (full cascade replication), D (remote read against cascade). Inline-migration seed for the test tenant (Region 75 + Delegation 33 + a handful of medico-social structures + `siege`); full inventory deferred to ADR-0028's cascade sync. `Structure.kind` enum-as-string drift-gated. |
| `docs/decisions/README.md` | ADR-0026 row: title updated, status stays `proposed`. New ADR-0027 row: `proposed`, tags `data, backend`. |
| `CLAUDE.md` | Roll-up clarifies: `0001 → 0025 accepted`; `ADR-0026 + ADR-0027 proposed`. `@RequireScope` Prisma-resolver roadmap entry references both ADRs + the ADR-0028 follow-up. No new Architecture bullet (entries land when their ADRs ship). |
## Why split
The cascade audit was the trigger. Cascade — APF's medico-social structure registry + Pléiades/Talentia HR integration — models `Structure` with a **seven-value type discriminator**: `medico_social`, `antenne`, `dispositif`, `entreprise_adaptee`, `mouvement`, `administratif`, `sanitaire`. Three of those (`antenne`, `dispositif`, part of `entreprise_adaptee`) **do not have a FINESS** by construction. Cascade carries FINESS / SIRET / SIREN / Pléiades `codePaie` / Talentia `codeCompta` on **separate per-source enrichment rows** (`StructureSourceFiness`, `StructureSourceSirene`, `StructureSourcePleiades`, `StructureSourceTalentia`), nullable and many-to-one against `Structure`.
The acteurs_plus audit confirmed: acteurs_plus does not store FINESS / SIRET / SIREN on its hierarchy entities at all — it uses a portal-internal `code` (unique string) + an `externalId` pointer.
The first ADR-0026 draft's `Etablissement.finess` PK excluded all non-medico-social structures by construction. The fix is **not** to make FINESS nullable on `Etablissement` (that smuggles the discriminator into absence-of-value semantics) — it is to adopt cascade's `Structure` + `kind` discriminator directly. Doing that inside ADR-0026 would have ballooned its scope; splitting is the cleaner shape:
- **ADR-0026** keeps a tight focus on identity (`Person` + `User` + `UserScope`). The Person model is unchanged from the first draft except for the email-dedup rewrite (already discussed before the audit landed).
- **ADR-0027** owns the org hierarchy with the cascade-aligned schema, the seeding posture, and the deferred parts (`Pole`, `Service`, arbitrary nesting, per-source enrichment) called out explicitly as ADR-0028's territory.
## ADR-0027 schema highlights
```prisma
model Structure {
// Portal-internal stable code. For medico-social structures we set
// code = FINESS (round-trips through scope literals + URLs cleanly).
// For non-medico-social structures: APF-internal slug ('siege',
// 'apf-bdx-merignac', 'ea-toulouse', 'mvt-national', …).
code String @id
name String
// Aligned with cascade's Structure.type discriminator. Drift-gated.
kind String // 'medico_social' | 'antenne' | 'dispositif'
// | 'entreprise_adaptee' | 'mouvement'
// | 'administratif' | 'siege'
finess String? @unique // 9 digits, NULL for non-medico-social
siret String? @unique // 14 chars, NULL when not SIRENE-registered
codePaie String? @unique // Pléiades 6-char, NULL in v1
delegationCode String? // NULL for siège, mouvement national
delegation Delegation? @relation(fields: [delegationCode], references: [code])
@@index([kind])
@@index([delegationCode])
}
```
The vocabulary mismatch — ADR-0025's scope kind name is `etablissement` but the value is now a `Structure.code` of any kind — is documented as a known wart, with a possible ADR-0025 amendment as the rename path if a maintainer trips over it.
## Notes for the reviewer
- **`Person.email` unique constraint dropped.** Two distinct humans genuinely can share an email (shared family alias, generic `info@` mailbox, error in an upstream feed). The first draft had `email String? @unique` carried over from a "let's use it as a v1 dedup key" line of thinking that the audit reshaped. The lifecycle now treats `entraOid` as the only natural key the v1 provisioner trusts; email is an attribute, indexed for operator-driven lookup (admin UI search, ADR-0028 reconciliation flow), not a constraint.
- **`UserScope.value` has no FK to ADR-0027 tables.** Deliberate: a scope can outlive its target (a structure decommissioned mid-quarter still has historical UserScope rows pointing at its code, which the audit log needs to read). Admin UI write path validates; runtime guard tolerates stale codes (they fail the resource match, not the sign-in).
- **The old open PR `docs/adr-0026-accept-and-tighten-lifecycle` is superseded by this one.** That branch promoted ADR-0026 (full first draft) to `accepted`. The Q1 / Q2 resolutions from that PR are preserved here — Q1 (no email-dedup) is the new ADR-0026 Lifecycle section; Q2 (inline-migration seed) moves to ADR-0027's "Seeding posture" section since it is org-hierarchy-specific. The old PR can be closed without merging.
- **No code changes**, so no `pnpm ci:check` impact. `pnpm exec prettier --check` clean on the four touched files.
## Test plan
- [x] `pnpm exec prettier --check` — clean on the four touched files.
- [x] ADR cross-references resolve (every `ADR-NNNN` link in the two ADRs round-trips; the new ADR-0028 reference is a known dangling marker for the future sync ADR).
- [ ] **Review focus** — cascade / acteurs_plus audit findings as cited in ADR-0027 §"Context"; the schema choices in ADR-0027 (kind enum, nullable FINESS/SIRET, internal `code` PK); the `Person.email` non-unique change in ADR-0026; the scope-kind vocabulary mismatch documented in ADR-0027.
## What's next (post-merge)
Per ADR-0026 §"Phasing" and ADR-0027 §"Phasing" — the two ADR PRs ship in parallel once accepted:
1. **ADR-0027 Implementation PR 1** — `Region` / `Delegation` / `Structure` Prisma schema + inline reference-data migration + `Structure.kind` catalogue + drift-gate extension.
2. **ADR-0026 Implementation PR 1** — `Person` / `User` / `UserScope` Prisma schema + `PersonAndUserProvisioner` called from `SessionEstablisher` + `Person.source` catalogue + drift-gate extension + updated `PrincipalBuilder`. Independent of (1) at the schema level — can ship in parallel.
3. **ADR-0026 Implementation PR 2** — `PrismaScopeResolver` replacing `StubScopeResolver` + `/admin/users/:id/scopes` admin screen + `prisma/seed.ts` populating the 19 test personas' `user_scopes` per `notes/test-tenant-role-assignments.md`. **Depends on both (1) and (2)** — the seed references `Structure.code` values from (1) and writes `UserScope` rows from (2).
4. **ADR-0028 (proposed)** — Pléiades + Acteurs+ + cascade syncs + facet schemas (Salarié / Élu / Adhérent / Bénévole / Bénéficiaire / PartenaireExterne) + operator-confirmed Person-reconciliation flow + schema extensions (`Pole`, `Service`, per-source enrichment) the sync needs.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #217
|
||
|
|
abd1f91809 |
fix(deps): update angular (#215)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular-devkit/core](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@angular-devkit%2fcore/21.2.11/21.2.12) | | [@angular-devkit/schematics](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@angular-devkit%2fschematics/21.2.11/21.2.12) | | [@angular/build](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@angular%2fbuild/21.2.11/21.2.12) | | [@angular/cdk](https://github.com/angular/components) | dependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@angular%2fcdk/21.2.11/21.2.12) | | [@angular/cli](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@angular%2fcli/21.2.11/21.2.12) | | [@angular/common](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/common)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fcommon/21.2.13/21.2.14) | | [@angular/compiler](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/compiler)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fcompiler/21.2.13/21.2.14) | | [@angular/compiler-cli](https://github.com/angular/angular/tree/main/packages/compiler-cli) ([source](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli)) | devDependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fcompiler-cli/21.2.13/21.2.14) | | [@angular/core](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/core)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fcore/21.2.13/21.2.14) | | [@angular/forms](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/forms)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fforms/21.2.13/21.2.14) | | [@angular/language-service](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/language-service)) | devDependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2flanguage-service/21.2.13/21.2.14) | | [@angular/localize](https://github.com/angular/angular) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2flocalize/21.2.13/21.2.14) | | [@angular/platform-browser](https://github.com/angular/angular) ([source](https://github.com/angular/angular/tree/HEAD/packages/platform-browser)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2fplatform-browser/21.2.13/21.2.14) | | [@angular/router](https://github.com/angular/angular/tree/main/packages/router) ([source](https://github.com/angular/angular/tree/HEAD/packages/router)) | dependencies | patch | [`21.2.13` -> `21.2.14`](https://renovatebot.com/diffs/npm/@angular%2frouter/21.2.13/21.2.14) | | [@schematics/angular](https://github.com/angular/angular-cli) | devDependencies | patch | [`21.2.11` -> `21.2.12`](https://renovatebot.com/diffs/npm/@schematics%2fangular/21.2.11/21.2.12) | --- ### Release Notes <details> <summary>angular/angular-cli (@​angular-devkit/core)</summary> ### [`v21.2.12`](https://github.com/angular/angular-cli/blob/HEAD/CHANGELOG.md#21212-2026-05-20) [Compare Source](https://github.com/angular/angular-cli/compare/v21.2.11...v21.2.12) ##### [@​angular/build](https://github.com/angular/build) | Commit | Type | Description | | --------------------------------------------------------------------------------------------------- | ---- | --------------------------------------------- | | [cbad57579](https://github.com/angular/angular-cli/commit/cbad57579adb5de7887985afbb2bf1f40adf3cb2) | fix | ignore virtual esbuild paths with (disabled): | <!-- CHANGELOG SPLIT MARKER --> </details> <details> <summary>angular/components (@​angular/cdk)</summary> ### [`v21.2.12`](https://github.com/angular/components/blob/HEAD/CHANGELOG.md#21212-plastic-moose-2026-05-20) [Compare Source](https://github.com/angular/components/compare/v21.2.11...v21.2.12) ##### material | Commit | Type | Description | | -- | -- | -- | | [da87be7646](https://github.com/angular/components/commit/da87be76464d76ec11ae922abd5f4c72c5b4ea3e) | fix | **datepicker:** ensure dates don't overflow on a small screen ([#​33281](https://github.com/angular/components/pull/33281)) | <!-- CHANGELOG SPLIT MARKER --> </details> <details> <summary>angular/angular (@​angular/common)</summary> ### [`v21.2.14`](https://github.com/angular/angular/blob/HEAD/CHANGELOG.md#21214-2026-05-20) [Compare Source](https://github.com/angular/angular/compare/v21.2.13...v21.2.14) ##### compiler | Commit | Type | Description | | -- | -- | -- | | [68282dff9f](https://github.com/angular/angular/commit/68282dff9f9ef46540cca4bd38fc1ab739c8a783) | fix | strip namespaced SVG script elements during template compilation | ##### core | Commit | Type | Description | | -- | -- | -- | | [c0f52272ed](https://github.com/angular/angular/commit/c0f52272ed337d4776bd4178cbbdc7f32037500f) | fix | do not insert todo when migrating void [@​Output](https://github.com/Output) | | [938a7f3edd](https://github.com/angular/angular/commit/938a7f3eddda97a39edb9edcc8b4dd970858b3a2) | fix | makes resource URL sanitizer lookup case-insensitive | | [0fb2724194](https://github.com/angular/angular/commit/0fb272419407a64a0a47096b03a911f4e7e83d79) | fix | reject script element as a dynamic component host | | [49113ac0ef](https://github.com/angular/angular/commit/49113ac0eff852d987b5acb28a9bbda0242842cd) | fix | visit ICU expressions in signal migration schematics | ##### router | Commit | Type | Description | | -- | -- | -- | | [099bf577ee](https://github.com/angular/angular/commit/099bf577ee8f0bab60593a8fd2a1de7d298e3cd6) | fix | skip scroll-to-top on initial navigation when hydrating | <!-- CHANGELOG SPLIT MARKER --> </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: #215 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
e8796e94f4 |
chore(deps): update dependency ts-jest to v29.4.11 (#214)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ts-jest](https://kulshekhar.github.io/ts-jest) ([source](https://github.com/kulshekhar/ts-jest)) | devDependencies | patch | [`29.4.10` -> `29.4.11`](https://renovatebot.com/diffs/npm/ts-jest/29.4.10/29.4.11) | --- ### Release Notes <details> <summary>kulshekhar/ts-jest (ts-jest)</summary> ### [`v29.4.11`](https://github.com/kulshekhar/ts-jest/blob/HEAD/CHANGELOG.md#29411-2026-05-21) [Compare Source](https://github.com/kulshekhar/ts-jest/compare/v29.4.10...v29.4.11) ##### Bug Fixes - preserve Bundler on the CJS path under TypeScript >= 6 ([3941818](https://github.com/kulshekhar/ts-jest/commit/39418187515f11b6584d35a4e3ddf50231f74936)), closes [#​4198](https://github.com/kulshekhar/ts-jest/issues/4198) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/214 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
8266e5b172 |
docs(adr-0026): person + user portal-side data model (proposed) (#213)
## Summary
[ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) shipped the authorization model with three stubs explicitly deferred to a follow-up ADR:
- `Principal.user.id` and `Principal.user.personId` carry the Entra `oid` as a placeholder — `apps/portal-bff/src/auth/principal-builder.ts` documents the seam.
- `StubScopeResolver` returns `[{ kind: 'unrestricted' }]` for every signed-in user; the per-persona scope values documented in `notes/test-tenant-role-assignments.md` have nowhere to live.
- `@RequireScope`'s `ScopableResource` shape references an `Etablissement` / `Delegation` / `Region` chain that has no Prisma table.
ADR-0026 specifies the portal-side data model that closes those stubs. **Decision-only, status `proposed`** — implementation lands in two PRs once accepted.
## What lands
| File | Change |
| --- | --- |
| `docs/decisions/0026-person-user-portal-data-model.md` | New ADR. MADR 4.0.0 format. Tags: `data`, `backend`, `security`. ~310 lines. |
| `docs/decisions/README.md` | One new index row for 0026 (status `proposed`, 2026-05-24). |
## ADR scope
The ADR commits the **schema** for:
- **`Person`** golden record — stable identity, can exist without a portal account (Pléiades pre-provisioning, dossier bénéficiaires, alumni). `source` field tracks provenance (`self-signin` in v1; `pleiades` / `acteurs-plus` join the catalogue with ADR-0027).
- **`User`** portal-account overlay — one-to-zero-or-one with Person, lazy-created on first OIDC callback. Portal-only state (`lastSignInAt`, future a11y preferences) rides here, not on the shared Person row.
- **`UserScope`** — confirms the migration whose shape ADR-0025 §"Sources of truth — apf_portal-side `user_scopes` table" already specified.
- **`Region` / `Delegation` / `Etablissement`** — organisational hierarchy with externally-meaningful codes (INSEE / French dept / FINESS) as primary keys, matching the on-the-wire shape of the scope literals (`etablissement:0330800013`, `delegation:33`).
The ADR **explicitly defers**:
- Facet schemas (`Salarie`, `Adherent`, `Benevole`, `Elu`, `Beneficiaire`, `PartenaireExterne`) — they track upstream-system shape and ride alongside their producing sync.
- Pléiades / Acteurs+ sync logic, reconciliation policy between sync-owned and admin-UI-owned fields.
Both deferrals point at **ADR-0027** (Pléiades + Acteurs+ syncs + facet schemas) as the next-up ADR.
## Notes for the reviewer
- **Why `Person` + `User` split and not a single table.** The "Considered Options" section walks through this. Option A (User-only) breaks down the moment a dossier holder who never signs in needs a stable identifier. Option D (Person with embedded facet columns) forces every Pléiades or Acteurs+ schema change through a table that both shapes share, which is exactly the kind of coupling that ADR-0027 needs the freedom to design out.
- **Why externally-meaningful primary keys for the hierarchy.** `Region.code` / `Delegation.code` / `Etablissement.finess` are INSEE / FINESS codes — stable across reorgs and already on every URL the portal will mint. Adding a separate UUID would force every consumer to indirect through it for no gain. The trade-off (no in-place rename — if a délégation merges, delete + re-insert with the new code) is bounded by how rarely INSEE rebases.
- **Lazy User creation at first sign-in.** v1 has no Pléiades data; the OIDC callback creates the Person + User pair the first time it sees a new `oid`. When Pléiades sync ships (ADR-0027), the same provisioner extends with a `Person.externalId` lookup before falling back to `email`. The schema does not change between the two regimes — only the provisioner's lookup order does.
- **`Person.source` and `Etablissement.kind` as enum-as-string + drift-gate extension.** The catalogue-drift gate ([ADR-0025 §"Confirmation"](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) and `scripts/check-catalogue-drift.mjs`) was built precisely to constrain hand-edited string columns. Extending it to assert every `Person.source` write is in the closed catalogue closes one of the soft spots in the v1 schema without standing up a Postgres ENUM.
- **PII posture.** `Person.firstName` / `lastName` / `email` are flagged in the ADR's Decision Drivers. The Pino redact list ([ADR-0012](docs/decisions/0012-observability-pino-opentelemetry.md)) and the audit-log salt ([ADR-0013](docs/decisions/0013-audit-trail-separated-postgres-append-only.md)) already cover Entra `oid`; the new PII fields ride the same posture (caller-redacted at the audit module, redacted at the Pino logger before the line ships).
- **What "two PRs" means in the More Information section.** PR 1: Prisma schema migration + `PersonAndUserProvisioner` called from `SessionEstablisher` + drift-gate extension. PR 2: `PrismaScopeResolver` replacing `StubScopeResolver` + `/admin/users/:id/scopes` screen + `prisma/seed.ts` populating the 19 test-tenant personas' `user_scopes` rows.
- **Backward compatibility for in-flight Redis sessions.** Sessions minted before the schema migration carry a Principal whose `user.id` is the Entra `oid` placeholder. The legacy-session bridge in [`apps/portal-bff/src/auth/principal-extractor.ts`](apps/portal-bff/src/auth/principal-extractor.ts) (added in #208) already handles this — when the migration deploys, those sessions continue to work for the 12 h absolute-TTL window, after which every session in Redis has the real `personId`.
## Open questions to resolve before acceptance
These were flagged in the ADR text and are worth surfacing here for the review pass:
- **`Person.email` as the v1 dedup key.** The ADR's "Bad, because" item — two Pléiades records sharing an email would crash the unique constraint. ADR-0027 will add the `externalId` precedence, but in the interim the v1 lazy-creator either trusts emails-are-unique (acceptable for the test tenant where every persona has a distinct one) or skips the dedup attempt and creates a fresh Person per `oid`. The ADR currently picks the dedup-by-email path; flag if the safer choice is "always fresh, reconcile later".
- **Should `Region` / `Delegation` / `Etablissement` migrations be seeded as part of the schema PR, or kept as a separate dataset import?** The ADR is silent on this; my default is "seed the geographic codes APF actually operates in" (a one-off SQL fixture in the migration directory). Worth confirming.
## Test plan
- [x] `pnpm exec prettier --check docs/decisions/0026-person-user-portal-data-model.md docs/decisions/README.md` — clean.
- [ ] **Review focus** — the chosen Option B vs the rejected A/C/D, the deferred facets list, the `Person.source` catalogue, the `Etablissement.kind` catalogue, and the two open questions above.
- [ ] Once accepted, the implementation phasing in the ADR's `§More Information` opens (2 PRs).
## What's next
- **This PR** — ADR-0026 ships as `proposed`.
- **Acceptance PR** — review pass, address open questions, promote `proposed → accepted` (same cadence as [#201 → #205](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) for ADR-0025).
- **Implementation PR 1** — Prisma schema + lazy provisioner.
- **Implementation PR 2** — `PrismaScopeResolver` + admin-UI scope-seeding + test-tenant seed.
- **ADR-0027** — Pléiades + Acteurs+ syncs + facet schemas (the deferred surface from this ADR).
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #213
|
||
|
|
ca3963b2e3 |
docs(claude.md): refresh Repository status with ADR-0022/0023/0025 shipments (#212)
## Summary `CLAUDE.md`'s **Repository status** section drifted from `main` while three ADRs landed back-to-back. The roadmap block still listed ADR-0022 (docs static site) and ADR-0023 (charts lib + audit dashboards) as upcoming chantiers even though both shipped, and the new `libs/shared/auth/` lib introduced by ADR-0025 was not in the lib-roots list. This PR refreshes the section to reflect what `main` actually carries today. No code touched. ## What lands | File | Change | | --- | --- | | `CLAUDE.md` | Repository status section rewritten: lib roots updated, three new "Shipped on `main`" bullets (ADR-0022 / 0023 / 0025), roadmap pruned and rebalanced around what is genuinely outstanding. | ## Notes for the reviewer - **Lib roots.** Was: "four lib roots (`libs/feature/`, `libs/shared/state`, `libs/shared/tokens`, `libs/shared/ui`, `libs/shared/util`)" — five entries called "four". Now: "seven lib roots" listing `libs/feature/auth`, `libs/shared/auth`, `libs/shared/charts`, `libs/shared/state`, `libs/shared/tokens`, `libs/shared/ui`, `libs/shared/util`. The count is correct and the new libs from ADR-0023 and ADR-0025 are explicit. - **CI line** now mentions the catalogue-drift gate alongside the standard `format:check / lint / test / build` quartet — readers grepping for "what CI runs" land on the actual gate set. - **AI relay entry merged with its live consumer.** The previous wording said "Live consumer (chatbot widget on `portal-shell`) and the proto-drift CI gate ship next" — both half-true: chatbot widget shipped (under `apps/portal-shell/src/app/features/chatbot/`), proto-drift gate did not. Entry now says chatbot is live and moves the proto-drift gate to the roadmap. - **Phase-3a admin app phrasing updated.** Previous wording: "business modules (CMS, menu management, user list, audit log viewer) not yet implemented". Two of those four exist on `main` today: `/admin/users` (user-list reader via `admin-users-reader.service.ts`) and `/admin/audit` (audit-log viewer + statistics + integrated charts). Entry now names them and notes that only CMS + menu management remain. - **Roadmap entry split for the guards.** The old line bundled `@RequireMfa()` and `@RequireAdmin()` as both "designed-in, awaiting first consumer route". `@RequireAdmin` is wired into every `/api/admin/*` controller today; only `@RequireMfa` is still consumer-less. Roadmap line narrowed to mention only `@RequireMfa`. - **ADR-0026 referenced as `(proposed)`.** The link target is a placeholder `(#)` because the ADR file does not exist yet — kept lowercase-light so the link does not render as a dead anchor in editors. Will become a real link in the same PR that drafts ADR-0026. ## Test plan - [x] `pnpm exec prettier --check CLAUDE.md` — clean. - [x] Manual cross-check against the workspace state: - `find libs/shared -maxdepth 1 -type d` matches the listed lib roots. - `ls docs/.vitepress/` confirms VitePress is wired. - `ls libs/shared/charts/src/lib/` confirms the three chart components exist. - `grep -l BarChart\\|DonutChart\\|StackedBarChart apps/portal-admin/src/app/pages/audit/` confirms the audit-page integration. - `ls scripts/check-catalogue-drift*` confirms the drift gate exists. - `grep -rn @RequireAdmin apps/portal-bff/src/admin/` confirms the admin gate is wired today. No CI gates affected — this is a doc-only change. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #212 |
||
|
|
2eb01f59b3 |
feat(ci): catalogue-drift gate for @RequirePrivilege/@RequireRole literals (ADR-0025) (#211)
## Summary
Phase 3 (and last) of [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md)'s implementation phasing (§"More Information" / §347): a small CI gate that asserts every string literal passed to the authorization decorators belongs to the closed catalogue declared in `libs/shared/auth/src/lib/authorization.types.ts`.
The TypeScript decorator signatures (`(...privileges: [Privilege, ...Privilege[]])`) already enforce this at compile time. The gate is **defence-in-depth** against the escape hatches the type system cannot catch:
- explicit `as Privilege` / `as FunctionalRole` casts (`'Portal.Foo' as Privilege`),
- the rare case where a developer hand-edits the catalogue type union without updating the runtime constant.
Runs in `<1s`, so it rides the existing `check` CI job rather than spinning up its own.
## What lands
| File | Role |
| --- | --- |
| `scripts/check-catalogue-drift.mjs` | The gate. TypeScript compiler API. Parses `authorization.types.ts` to extract the catalogues, walks every `.ts` file under `apps/` and `libs/`, finds each `CallExpression` whose callee identifier matches `RequirePrivilege` / `RequireRole`, validates every string-literal argument. Non-literal args are skipped (the TypeScript signature catches them already). Reports grouped by file with `line:column` per violation, exits 1 on drift. |
| `scripts/check-catalogue-drift.spec.mjs` | 13 tests via `node --test` (built-in runner, no Vitest dep). Covers catalogue parsing, file-level violation detection, workspace-wide aggregation, skipped folders, gen-stub exclusion, line/column reporting. |
| `package.json` | `ci:catalogue-drift` + `ci:catalogue-drift:test` scripts. |
| `.gitea/workflows/ci.yml` | The existing `check` job now runs the gate's unit tests first (fail-fast if the gate itself is broken), then the gate against the live workspace. |
## Notes for the reviewer
- **Why not an ESLint custom rule.** ADR-0025 §347 floats either option. The ESLint route would give editor-time feedback (red squiggles) but means standing up a local ESLint plugin lib — net ~300 lines of plumbing for a gate the TypeScript signature already enforces in real time via the literal-union types. The pnpm-script route mirrors the existing `ci:gzip-budgets` pattern; ~250 lines including tests; runs in the same `check` CI job that already installs deps. Editor feedback is **already provided by `tsc`**, so the gate's job reduces to "catch escape hatches in CI", which the script does fine.
- **Why `node --test` rather than Vitest / Jest.** The script lives in `scripts/`, outside any Nx project. Wiring Vitest for one spec file would mean a vitest.config + tsconfig.spec + Nx project just to host it. Node's built-in test runner ships with the runtime we already pin (Node 24 in `.nvmrc`), no config, ~250ms wall clock for 13 tests.
- **Generated gRPC stubs are skipped.** `apps/portal-bff/src/grpc/gen/apf-ai/common.ts` defines a `roles: string[]` proto field used by the AI-bridge — entirely unrelated to the ADR-0025 functional-role catalogue. The skip list is explicit (`SKIPPED_SUBPATHS`); adding a new codegen output later is one line.
- **Spec files are NOT skipped.** `auth-guards.persona-matrix.spec.ts` references catalogue values via the decorators in its test fixtures. Those are deliberate references and must stay in sync — if a future ADR amendment removes a role, the spec catches it on the same run as the production code. Explicitly tested in `does NOT skip spec files`.
- **Non-literal arguments (variable indirection) are skipped.** A pattern like `const slug = getRole(); @RequireRole(slug)` would not be caught by string-literal inspection. This is intentional: the TypeScript decorator signature already requires `slug` to be typed `FunctionalRole`, so the type system handles it; the gate's value-add is on literal misspellings the type system cannot see past an `as Privilege` cast.
- **Self-test confirmed the gate works.** Injected `RequirePrivilege('Portal.RogueDrift')` and `RequireRole('rogue-role-x')` into an existing spec, ran `pnpm ci:catalogue-drift`, observed:
```
catalogue-drift: violations found
apps/portal-bff/src/auth/require-privilege.guard.spec.ts
135:18 @RequirePrivilege('Portal.RogueDrift') — not in PRIVILEGES
136:13 @RequireRole('rogue-role-x') — not in FUNCTIONAL_ROLES
```
Exit code 1. Reverted the injection; gate green again.
- **Adding a third decorator** (per ADR-0025's anticipated growth) is one line in `DECORATOR_TO_CATALOGUE` plus the matching test fixture. The script does not hardcode the decorator name list beyond that map.
- **Why no scope-kind check.** `@RequireScope` takes an `(req) => ScopableResource` extractor, not literals. The scope `kind` values are constrained by the `ScopeKind` discriminated union, which TypeScript enforces at every `{ kind: ... }` construction site. No literal escape hatch worth gating in v1.
## Test plan
- [x] `pnpm ci:catalogue-drift:test` — 13/13 green via `node --test`.
- [x] `pnpm ci:catalogue-drift` — clean against the live workspace: `catalogue-drift: clean (catalogues: 4 privileges, 24 roles).`
- [x] Self-test by injection — script catches `'Portal.RogueDrift'` and `'rogue-role-x'` with file:line:column, exits 1.
- [x] `pnpm exec prettier --check` on the new files — clean.
- [ ] CI run on this PR exercises the new step in the `check` job.
## What's next
ADR-0025's phasing closes with this PR. Remaining authorization work waits on [ADR-0026](docs/decisions/) (proposed) — the `Person` + `User` schema brings the Prisma-backed `user_scopes` table; that PR replaces `StubScopeResolver` with a `PrismaScopeResolver` and unlocks the first concrete `@RequireScope` consumer surfaces.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #211
|
||
|
|
77af6951c9 |
fix(deps): bump uuid to >=11.1.1 via pnpm.overrides (GHSA-w5hq-g745-h8pq) (#210)
## Summary `pnpm ci:audit` was failing on `main` with one moderate-severity finding: ``` uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided GHSA-w5hq-g745-h8pq Vulnerable: uuid < 11.1.1 Path: . > @lhci/cli@0.15.1 > uuid@8.3.2 ``` `@lhci/cli@0.15.1` is the latest release and still ships `uuid@8.3.2`; no upstream fix yet. Renovate cannot solve this on its own. ## What lands | File | Change | | --- | --- | | `package.json` | `pnpm.overrides`: add `"uuid@<11.1.1": ">=11.1.1"` alongside the existing axios / ajv / brace-expansion / esbuild / follow-redirects / ip-address / protobufjs / tmp / ws / yaml entries. | | `pnpm-lock.yaml` | Refreshed. Both `@lhci/cli` and `sockjs` (transitive via the rspack / webpack dev-servers) now resolve to `uuid@14.0.0` — the same major already in the tree via `mermaid`. Net `uuid` versions: 2 → 1. | ## Notes for the reviewer - **Why an override and not an `auditConfig.ignoreGhsas`.** The repo already uses `pnpm.overrides` for 10 prior advisories on transitive deps with no patched release in their consumer — same pattern applied here. An exemption-document path was drafted then dropped: forcing the fix is cleaner than waving away the warning, and the upgrade actually works. - **Why our usage was not at risk (context, not justification to skip).** The advisory affects `uuid.v3` / `uuid.v5` / `uuid.v6` when called with the optional `buf` argument. Both consumers in the tree only use `uuid.v4()` (random): - `@lhci/cli/src/collect/node-runner.js:65` — `\`flags-${uuid.v4()}.json\`` - `sockjs/lib/transport.js:9` — `uuidv4 = require('uuid').v4` But the audit gate is set to `moderate` per [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md); every flagged advisory blocks merge regardless of reachability. Forcing the fix is the documented response. - **Why `uuid@14` works for the CJS consumers.** `uuid@14` is ESM-only (`type: module` + conditional `exports`). `@lhci/cli` does `const uuid = require('uuid')` which is a CJS-requires-ESM pattern. Node `>= 22.12` supports it natively via the stable `require(esm)` capability; the workspace pins Node 24 in [`.nvmrc`](.nvmrc), so the consumer is fine. Verified locally: ``` $ node -e "require('@lhci/cli/src/collect/node-runner.js')" # no error ``` - **Side benefit.** `uuid@8.3.2` was also pulled in transitively via `sockjs`. After the override, the dep tree consolidates onto a single `uuid@14.0.0` — removes the `Found 2 versions of uuid` dedup notice and aligns with the version `mermaid` already uses. - **Closure condition.** When `@lhci/cli` ships a release with `uuid >= 11.1.1` (or with `uuid@14` directly), Renovate will surface the bump; this override row becomes redundant and can be deleted. Same removal cadence as the other `pnpm.overrides` entries. ## Test plan - [x] `pnpm ci:audit` — clean: `No known vulnerabilities found`. - [x] `pnpm ci:check` — 13 projects green (no behavioural change against the previous run). - [x] Loaded `@lhci/cli`'s node-runner module under the override to confirm `require('uuid')` resolves without error on Node 24. - [ ] Post-merge CI on `main` runs `pnpm ci:audit` cleanly. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #210 |
||
|
|
5f1ef2444a |
fix(ci): align shared-auth build outputPath with tsconfig.lib.json outDir (#209)
## Summary `pnpm ci:check` was failing on `main` after #208 merged: `portal-bff:build` raised `TS6305 Output file 'dist/out-tsc/libs/shared/auth/src/index.d.ts' has not been built from source file 'libs/shared/auth/src/index.ts'` against every file in `apps/portal-bff/src/auth/` that imports from `shared-auth`. Fresh CI runners hit it; my local runs masked it because a manual `tsc --build` during development had populated both candidate output dirs. ## Root cause When `nx sync` was first run for #206, it added a TypeScript project reference to `apps/portal-bff/tsconfig.app.json`: ```json "references": [{ "path": "../../libs/shared/auth" }] ``` `portal-bff`'s build runs webpack with `compiler: 'tsc'` against that tsconfig, so tsc validates the referenced project's outputs exist at the location its tsconfig.lib.json declares as `outDir` — `dist/out-tsc/libs/shared/auth/`. But `libs/shared/auth/project.json` set `outputPath: dist/libs/shared/auth` and Nx's `@nx/js:tsc` executor honours its own `outputPath` over the tsconfig's `outDir`, emitting the declarations to `dist/libs/shared/auth/` instead. tsc looks at the empty `dist/out-tsc/libs/shared/auth/` location and fails with TS6305. This was harmless until #208: the previous PRs only had a single `import` from `shared-auth` (in `principal-builder.ts`), and apparently tsc's incremental cache from the local dev box's pre-fix runs was reused in CI. #208 added enough new imports across enough files that the missing declarations became unavoidable. ## What lands | File | Change | | --- | --- | | `libs/shared/auth/project.json` | `outputPath: dist/libs/shared/auth` → `dist/out-tsc/libs/shared/auth`. Aligns Nx's emit location with the tsconfig.lib.json's declared `outDir`, so the project-reference validation finds the declaration files. | One-line change, no other files touched. ## Notes for the reviewer - **Why this didn't show in the green CI on #206 / #208.** Both PRs ran the same `nx affected -t format:check lint test build` recipe and both reported green. The likely explanation is that during PR-level runs Nx's distributed cache hit on the `portal-bff:build` task (the inputs included the source files but the cache stored the *result* of the build, which was green from an earlier successful run before the project reference landed). Post-merge runs on `main` evict the cache differently (different commit SHA → cache miss → tsc actually re-runs and hits the missing declarations). - **Why `shared-util`, `shared-tokens`, etc. don't have the same problem.** They share the exact same outputPath / outDir mismatch in their project.json + tsconfig.lib.json, but they're only consumed by the Angular SPAs (`portal-shell`, `portal-admin`). `@angular/build` does not validate TypeScript project references the same way `webpack + compiler: 'tsc'` does, so the mismatch sits silently. `shared-auth` is the first lib in the workspace that crosses into `portal-bff`, which is why the trap surfaced now. - **Alternative considered: drop the project reference in `portal-bff/tsconfig.app.json`.** Path aliases in `tsconfig.base.json` resolve `shared-auth` to source files directly, so webpack would still find the imports. Rejected because `nx sync` adds project references back automatically — the alignment-of-paths fix is more durable than a configuration deletion. - **Why not align the other shared libs preemptively.** They aren't broken — their project references aren't being validated. Touching them in the same PR would expand the diff for zero current benefit. The pattern documented here is what the next lib that's consumed by `portal-bff` will need; the precedent is on the books. - **Locally reproducible.** `pnpm nx reset && rm -rf dist .nx/cache .nx/workspace-data && pnpm ci:check` reproduces the original failure on the previous commit and passes on this commit. ## Test plan - [x] `pnpm nx reset && rm -rf dist .nx/cache .nx/workspace-data && pnpm nx affected -t format:check lint test build --base=main --skip-nx-cache` — 3 projects green from a fully cold state. - [ ] Post-merge CI on `main` runs through `pnpm ci:check` cleanly. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #209 |
||
|
|
7d91da691b |
feat(auth): @RequirePrivilege/@RequireRole/@RequireScope guards (ADR-0025) (#208)
## Summary Phase 2 of [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md)'s implementation phasing (§"More Information"). The three new route decorators land — `@RequirePrivilege`, `@RequireRole`, `@RequireScope` — alongside the migration of the legacy `@RequireAdmin` to read from `principal.privileges`. Each guard consumes the session-resident `Principal` built by [#206](#206), evaluates its requirement, and either passes or emits a 403 + audit row. The drift-CI gate (phase 3) is **not** in this PR — it lands next, once the catalogue is being referenced from real route handlers. ## What lands ### Shared lib (`libs/shared/auth`) | File | Role | | --- | --- | | `src/lib/principal-matchers.ts` | Pure functions: `principalHasAnyPrivilege` / `principalHasAnyRole` / `principalCoversResource`. The matchers live in the shared lib so the SPA can render UI predicates with the same logic the BFF enforces server-side. | | Same file | `ScopableResource` type — optional FINESS / delegation / region / siege parentage. Callers populate the subset their route resource exposes; the matcher iterates and returns true on the first scope that covers a present field. | | `src/lib/principal-matchers.spec.ts` | 24 unit tests covering OR-composition, the empty-requirement degenerate case (treated as "no constraint" — pass), every scope kind, and the resource-side parentage chain. | ### BFF guards + decorators (`apps/portal-bff/src/auth`) | File | Role | | --- | --- | | `require-privilege.{decorator,guard}.ts` | `@RequirePrivilege('Portal.Admin', ...)` — type-locked to the closed `Privilege` catalogue; multiple values OR-combine. | | `require-role.{decorator,guard}.ts` | `@RequireRole('rh', ...)` — same shape, against `FunctionalRole`. | | `require-scope.{decorator,guard}.ts` | `@RequireScope(req => extractResource(req))` — extractor can be async (Prisma lookup pattern); returning `null` is treated as denial with empty `required[]`. | | `principal-extractor.ts` | Single read of `Principal` off the session, with a legacy-session bridge for principals minted before [#206](#206) landed — filters `user.roles` for `Portal.*` values and synthesises an `unrestricted` scope. After the 12 h absolute-TTL window post-deploy, the bridge becomes dead code. | | `principal-extractor.spec.ts` | 7 tests covering both code paths. | | `require-{privilege,role,scope}.guard.spec.ts` | Unit tests for each guard: 401 on anonymous, 403 + audit on denial, pass on match, generic `forbidden` response body (no role/privilege/resource hint leaks). | | `auth-guards.persona-matrix.spec.ts` | Integration tests: each of the 19 test-tenant personas walked through 5 representative privilege guards + 6 representative role guards. The matrix proves the contracts hold against the real provisioning, not just synthesised principals. | ### Audit module | File | Change | | --- | --- | | `audit.types.ts` | New `AuthorizationDeniedInput` discriminated by `kind: 'privilege' \| 'role' \| 'scope'`. Carries `required[]` and `held[]` arrays so an auditor can pivot on "tried admin while logged in as RH" patterns without joining anything. | | `audit.service.ts` | New `authorizationDenied()` method emitting the `auth.authorization_denied` event type. Distinct from the existing `admin.access_denied` so admin-surface signals stay clean. | | `audit.service.spec.ts` | 3 new tests covering the three `kind` values. | ### Legacy guard migration | File | Change | | --- | --- | | `admin/admin-role.guard.ts` | Reads `principal.privileges` instead of `user.roles`. Public `@RequireAdmin()` API unchanged; `admin.access_denied` event type unchanged. The audit row's `rolesHeld` field now carries `Portal.*` values rather than the raw legacy `roles` claim. | | `admin/admin-role.guard.spec.ts` | Rewritten to exercise `session.principal`-shaped sessions, with a dedicated legacy-bridge sub-suite that asserts pre-ADR-0025 sessions still work. | | `me/me.controller.ts` | `capabilities().canAccessAdmin` reads `principal.privileges` via the same extractor. | | `me/me.controller.spec.ts` | Rewritten against the principal shape. | ### AuthModule wiring | File | Change | | --- | --- | | `auth/auth.module.ts` | Three new guards registered as providers and re-exported. | ## Notes for the reviewer - **Composition semantics.** Within a single decorator, values OR-combine (`@RequireRole('rh', 'comptable')` matches anyone with either). Stacking decorators AND-combines at the Nest level — `@RequireRole('rh') @RequirePrivilege('Portal.Admin')` requires both, because each guard runs separately and a single denial stops the request. The empty-requirement case is rejected at the type level by the tuple `[Privilege, ...Privilege[]]` so a route can not opt out of authorization by passing zero values. - **Why `auth.authorization_denied` and not `admin.access_denied` for the new guards.** ADR-0025 §347 originally said "writes an `admin.access_denied` row", but reusing the `admin.*` namespace would have meant the future `@RequireRole('rh')` on a non-admin HR route ships an event whose type implies an admin surface. The new event type lives in the `auth.*` namespace where the guards live; a `kind` discriminator on the payload tells privilege / role / scope denials apart. The legacy `admin.access_denied` event continues unchanged for `AdminRoleGuard`. - **The legacy-session bridge is short-lived.** After the 12 h absolute-TTL window from this PR's deploy, every session in Redis has a `principal` field and the bridge's `else` branch becomes unreachable. The bridge keeps `@RequireAdmin` + `MeController` functional during that window without needing a forced re-auth campaign. - **Why a `ScopableResource` shape rather than a `Resource | Resource | ...` union per kind.** Routes protect heterogeneous resource types (`Etablissement`, `Dossier`, `Person`), each exposing a different subset of the parentage chain. The optional-field shape lets each route populate what its resource carries and lets the matcher iterate over the principal's scopes once. The cost is that a route author who forgets to populate `delegationCode` on an `Etablissement` resource locks delegation-scoped users out — a typing exercise that the next round of work (concrete consumers) will surface naturally. - **Why `principalCoversResource` deny on doubt.** When a route's extractor returns a resource that lacks the parentage `delegationCode` field, a `delegation:33` scope no longer matches — deny is the safe direction. An over-restrictive deny shows up in the audit log (operator can fix the extractor); an over-permissive pass would silently leak data. The non-transitivity of the parentage chain is an intentional contract. - **Why migrate `MeController` in the same PR.** The `canAccessAdmin` flag reads the same axis (`Portal.Admin` privilege). Leaving it on the legacy `user.roles` shape while everything else moves to `principal.privileges` would create internal inconsistency; a future reviewer would rightfully ask "why?". The migration is three lines plus a spec rewrite. - **The drift-CI gate is the next PR.** ADR-0025 §"More Information" step 3. ESLint custom rule (or a `pnpm run` script) grepping every `@RequirePrivilege('...')` / `@RequireRole('...')` / scope-kind literal in the codebase and asserting each one exists in the catalogue. Now there is something to grep for (the decorators and their tests reference catalogue values), so the gate is well-scoped to land standalone. - **No real consumers of `@RequireScope` yet.** The scope guard ships with a fully exercised contract (extractor signature, async support, audit shape, generic-forbidden body) but no live route. The first consumer surface lands with the `Person` + `User` schema (proposed ADR-0026) and the resource-loading routes that follow. ## Test plan - [x] `pnpm nx affected -t lint test build --base=main` — 3 projects green - `portal-bff`: 741 tests pass (was 497 in #206 — +244 new: matchers/guards/persona-matrix/audit/extractor). - `shared-auth`: 41 tests pass (was 17 in #206 — +24 new matcher tests). - `portal-bff-e2e`: lint green. - [x] `pnpm nx format:check` — clean after `pnpm nx format:write`. - [ ] **Review focus** — the 19-persona matrix (each persona walked through 5 privilege guards + 6 role guards); the legacy-session bridge in `principal-extractor.ts` and its tests; the `admin.access_denied` audit payload now carries `Portal.*` values rather than legacy `roles` (intentional, called out above); the `auth.authorization_denied` event-type rationale. ## What's next Per ADR-0025 §"More Information" phasing: 1. ✅ #206 — types + Principal builder + group-to-role mapping skeleton. 2. ✅ **This PR** — the three new decorators + guards, legacy `@RequireAdmin` migration. 3. **Next PR** — drift CI gate. ESLint custom rule (or `pnpm run` script) that asserts every `@RequireX('...')` literal in code is in the closed catalogue. 4. **Depends on ADR-0026** — `user_scopes` Prisma table + seed + `PrismaScopeResolver` replacing `StubScopeResolver`, then the first concrete consumers of `@RequireScope`. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #208 |
||
|
|
a34709613f |
fix(ci): refresh lockfile for the new shared-auth lib (#207)
## Summary The auth-catalogue skeleton PR (#206) added `libs/shared/auth/` with three dev dependencies (`tslib`, `vitest`, `@nx/vite`) in the new lib's `package.json` but did not refresh `pnpm-lock.yaml`. As a result the post-merge `pnpm install --frozen-lockfile` step in CI on `main` fails with: ``` ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with <ROOT>/libs/shared/auth/package.json specifiers in the lockfile don't match specifiers in package.json: * 3 dependencies were added: tslib@^2.3.0, vitest@^4.0.8, @nx/vite@^22.7.1 ``` Running `pnpm install` locally adds the missing `importers["libs/shared/auth"]` block. No other dependency moves — every other version is pinned to what `main` already resolved. ## What lands | File | Change | | --- | --- | | `pnpm-lock.yaml` | +12 lines: new `importers["libs/shared/auth"]` entry with the three deps resolved to `tslib@2.8.1`, `vitest@4.1.6`, `@nx/vite@22.7.2` — same versions as the other `libs/shared/*` libs already use. | ## Notes for the reviewer - **Why this didn't surface during #206's CI run.** PR-level CI runs against the branch's `pnpm-lock.yaml` *after* `pnpm install` had refreshed it locally during the work. The lockfile delta should have been part of #206 but was missed at commit time. Post-merge CI on `main` is the first run that exercises a clean `--frozen-lockfile` against the merged tree, which is when the gap shows. - **Why not amend #206.** It's already on `main`. A separate small fix-up PR is cleaner than re-opening the merged history. - **Preventing recurrence.** Standard Nx flow: every PR that creates a new lib or moves a dep should run `pnpm install` and stage the lockfile alongside the source. A future drift-gate (mentioned in ADR-0025 §"Confirmation" for catalogue drift) could be extended to also assert lockfile-vs-package-json alignment, but that's out of scope for this fix. ## Test plan - [x] `pnpm install --frozen-lockfile` succeeds locally on this branch. - [ ] Post-merge CI on `main` runs through the install step cleanly. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #207 |
||
|
|
273d96551f |
chore(deps): update dependency @swc/core to v1.15.40 (#204)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@swc/core](https://swc.rs) ([source](https://github.com/swc-project/swc/tree/HEAD/packages/core)) | devDependencies | patch | [`1.15.33` -> `1.15.40`](https://renovatebot.com/diffs/npm/@swc%2fcore/1.15.33/1.15.40) | --- ### Release Notes <details> <summary>swc-project/swc (@​swc/core)</summary> ### [`v1.15.40`](https://github.com/swc-project/swc/blob/HEAD/CHANGELOG.md#11540---2026-05-23) [Compare Source](https://github.com/swc-project/swc/compare/v1.15.33...v1.15.40) ##### Bug Fixes - **(es/minifier)** Preserve args for destructured callbacks ([#​11830](https://github.com/swc-project/swc/issues/11830)) ([21873b0](https://github.com/swc-project/swc/commit/21873b06df3fd62d952a21cf879e14d11d4b39d7)) - **(es/minifier)** Avoid generating mangled property names that collide with existing properties ([#​11839](https://github.com/swc-project/swc/issues/11839)) ([9b4fab5](https://github.com/swc-project/swc/commit/9b4fab58c90256a6da688de87ea405225a5a6fdb)) - **(es/minifier)** Respect ecma for iife temp vars ([#​11873](https://github.com/swc-project/swc/issues/11873)) ([e481934](https://github.com/swc-project/swc/commit/e481934a63c0ee891e4a770c4f0cd5ec3fd8624e)) - **(es/minifier)** Preserve default parameter object props ([#​11884](https://github.com/swc-project/swc/issues/11884)) ([71ff84f](https://github.com/swc-project/swc/commit/71ff84f19762306ab9b86accb29eb6ed83c46f84)) - **(es/parser)** Reject object-rest assignment to array/object literal ([#​11875](https://github.com/swc-project/swc/issues/11875)) ([7b57d1f](https://github.com/swc-project/swc/commit/7b57d1f8717d8bf6be0b617b04bc6e219a2b3775)) - **(es/parser)** Reject object rest assignment to literals ([#​11881](https://github.com/swc-project/swc/issues/11881)) ([4ec2eaf](https://github.com/swc-project/swc/commit/4ec2eaf4d89ddd95293b8f09169a88b0434c5a13)) - **(es/react)** Exclude self-recursive hooks from refresh dependency array ([#​11838](https://github.com/swc-project/swc/issues/11838)) ([9101c71](https://github.com/swc-project/swc/commit/9101c719fa8f3f5cb410d716d4f50544650cd81e)) - **(ts/fast-dts)** Strip definite assertions in dts ([#​11858](https://github.com/swc-project/swc/issues/11858)) ([2ab1b8a](https://github.com/swc-project/swc/commit/2ab1b8a50f2af3d8b4c42d6c4dd4f2051940cae0)) - **(ts/fast-strip)** Reject unsafe assertion erasure in binary expressions ([#​11828](https://github.com/swc-project/swc/issues/11828)) ([aa5b539](https://github.com/swc-project/swc/commit/aa5b539b277dbf4c68c87380d16f4b8713145df3)) - **(typescript)** Strip parameter binding defaults in dts ([#​11857](https://github.com/swc-project/swc/issues/11857)) ([800bc17](https://github.com/swc-project/swc/commit/800bc170334a74191eb5ae21e3bfc96bf6f7fe56)) ##### Documentation - Update agent guidance ([#​11842](https://github.com/swc-project/swc/issues/11842)) ([bf2d015](https://github.com/swc-project/swc/commit/bf2d0154cf8b66fdab16085585fda0086d297a64)) - Add security policy ([#​11876](https://github.com/swc-project/swc/issues/11876)) ([6c43c2d](https://github.com/swc-project/swc/commit/6c43c2de9cb9d5516b0ac87101345940964e943e)) - Clarify security scope for npm packages ([#​11877](https://github.com/swc-project/swc/issues/11877)) ([4662db8](https://github.com/swc-project/swc/commit/4662db8fe3e503f298a285697ea63ecc1ca3b958)) - Clarify untrusted input security model ([#​11882](https://github.com/swc-project/swc/issues/11882)) ([5463777](https://github.com/swc-project/swc/commit/546377770e164aead174404fb678319c9c56a9dc)) ##### Features - **(es/minifier)** Fine grained effect analysis of class ([#​11814](https://github.com/swc-project/swc/issues/11814)) ([c9058ad](https://github.com/swc-project/swc/commit/c9058adb5bb7d6bbe354e6136685271f722354a0)) - **(swc\_cli)** Implement all features for `swc_cli` ([#​11797](https://github.com/swc-project/swc/issues/11797)) ([9300ede](https://github.com/swc-project/swc/commit/9300ede1d495463042da1db11754c76057a50954)) ##### Miscellaneous Tasks - **(es/minifier)** Fix typo in debug log ([#​11866](https://github.com/swc-project/swc/issues/11866)) ([3de0254](https://github.com/swc-project/swc/commit/3de0254db7fae5a2883af78b8b7d57af4cb94531)) - **(html)** Add webcontainer fallback for `@swc/html` ([#​11860](https://github.com/swc-project/swc/issues/11860)) ([7692eed](https://github.com/swc-project/swc/commit/7692eed981916bb01a3c4c231b3af012d4993d9e)) ##### Performance - **(ecma)** Reduce transformer compat overhead ([#​11856](https://github.com/swc-project/swc/issues/11856)) ([d03cb71](https://github.com/swc-project/swc/commit/d03cb71fb8b39f01f0ad704473109294e52e07fd)) - **(es/codegen)** Speed up JsWriter position and srcmap tracking ([#​11867](https://github.com/swc-project/swc/issues/11867)) ([dbceade](https://github.com/swc-project/swc/commit/dbceade22809ac9a9cb7548456ab335df26fb046)) - **(es/codegen)** Remove JsWriter last\_srcmap cache ([#​11869](https://github.com/swc-project/swc/issues/11869)) ([3bc1c2b](https://github.com/swc-project/swc/commit/3bc1c2b9b2594b05478dc4240977b981d6f8521b)) - **(es/minifier)** Reduce minifier profiling hotspots ([#​11853](https://github.com/swc-project/swc/issues/11853)) ([28c1091](https://github.com/swc-project/swc/commit/28c1091adb2c6f6d0e46daa5595908d1ba6fccb7)) - Optimize es parser comment finalization ([#​11852](https://github.com/swc-project/swc/issues/11852)) ([2959ddf](https://github.com/swc-project/swc/commit/2959ddf87af0ac95eb5176180782819bd1073d66)) ##### Testing - **(es/minifier)** Move issue\_11835 fixture out of terser folder ([#​11840](https://github.com/swc-project/swc/issues/11840)) ([3dd3431](https://github.com/swc-project/swc/commit/3dd34310d429baff6e8d1a6393266c648684d3c6)) ##### Ci - Update corepack in publish docker jobs ([#​11885](https://github.com/swc-project/swc/issues/11885)) ([9a7d954](https://github.com/swc-project/swc/commit/9a7d954c4939b12da4c60023eba50d6df8086fd7)) - Pass publish docker env explicitly ([#​11888](https://github.com/swc-project/swc/issues/11888)) ([c5f7547](https://github.com/swc-project/swc/commit/c5f7547cf68a4803aec3e88901e0d6b57ebbeb55)) - Lock issues closed by merged prs ([#​11887](https://github.com/swc-project/swc/issues/11887)) ([6bd74e5](https://github.com/swc-project/swc/commit/6bd74e5683ed43640db64f78dc74001a056c1bfa)) - Provide aarch64 musl linker in publish job ([#​11889](https://github.com/swc-project/swc/issues/11889)) ([20234fd](https://github.com/swc-project/swc/commit/20234fd265f8f86f0c81c31c36e467d405a04d01)) - Fix publish musl linker and windows tests ([#​11890](https://github.com/swc-project/swc/issues/11890)) ([a798a23](https://github.com/swc-project/swc/commit/a798a23e5f5018e5c01f874457aa20370c0d7058)) - Make minifier test path explicit ([#​11891](https://github.com/swc-project/swc/issues/11891)) ([e7cba97](https://github.com/swc-project/swc/commit/e7cba972ff25565208c4448accab19c259e6947c)) ##### Security - Save CI caches only on main ([#​11848](https://github.com/swc-project/swc/issues/11848)) ([7582529](https://github.com/swc-project/swc/commit/75825293150b548216bf5c08531e1850bd064fcb)) - Update rkyv and Rust dependencies ([#​11851](https://github.com/swc-project/swc/issues/11851)) ([20d92eb](https://github.com/swc-project/swc/commit/20d92eb3c8dee378f046a6bff839913600a1fbdb)) - Harden PR workflow permissions ([#​11849](https://github.com/swc-project/swc/issues/11849)) ([e199564](https://github.com/swc-project/swc/commit/e199564ebaae88ec121a777cf0ec4eec9644aed5)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/204 Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com> |
||
|
|
12136f7a8a |
feat(auth): authorization catalogues + Principal builder skeleton (ADR-0025) (#206)
## Summary First implementation PR after [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) was accepted in #205. Lands the closed-set catalogues + the OIDC-callback hook that composes the session-resident `Principal`. No new guards yet — those come in the next PR per the ADR's `§More Information` phasing. ## What lands **New shared library: `libs/shared/auth/`** (`scope:shared`, `type:shared`, framework-agnostic, vitest) | File | Role | | --- | --- | | `src/lib/authorization.types.ts` | Closed catalogues: `PRIVILEGES` (4), `FUNCTIONAL_ROLES` (24), `SCOPE_KINDS` (6). `Principal`, `Scope` types. `isPrivilege` / `isFunctionalRole` / `isScopeKind` type-guards for the drift gate. | | `src/lib/entra-group-to-role.ts` | `parseEntraGroupMap` (validates GUID + slug closedness + dedup) + `EntraGroupToRoleResolver` (translates the Entra `groups` claim into ordered `apf-role-*` slugs, reports unknown GUIDs via callback). | | `src/lib/*.spec.ts` | 17 unit tests against the catalogue counts + resolver contracts. | **BFF wiring** (`apps/portal-bff/src/auth/` + `src/config/`) | File | Role | | --- | --- | | `auth.service.ts` | Extracts the Entra `groups` claim. `AuthenticatedUser` grows `groups: readonly string[]`. | | `principal-builder.ts` | Composes the three axes at sign-in. Filters `roles` claim through `isPrivilege` (drops + WARNs on unknown), resolves `groups` claim through the `EntraGroupToRoleResolver` (drops + WARNs on unknown). | | `scope-resolver.ts` | `ScopeResolver` abstract class + `StubScopeResolver` returning `[{ kind: 'unrestricted' }]` (per ADR-0025 §331 — replaced by a Prisma implementation when ADR-0026's `user_scopes` table lands). | | `session-establisher.service.ts` | Calls `PrincipalBuilder.build()` once, persists the result as `req.session.principal`. The legacy `req.session.user` shape is untouched (audit + AI bridge keep working). | | `session.types.ts` | Adds optional `principal?: Principal` field on the express-session augmentation. | | `entra-group-to-role.token.ts` | DI token for the lazily-loaded resolver. | | `config/load-entra-group-map.ts` | Reads the gitignored `infra/<env>-tenant.entra.json` at boot. Missing path → empty resolver + WARN. Malformed file → hard fail (alternative would be silently dropping a role). | **Tests against the 19 test-tenant personas** (`principal-builder.spec.ts`) Every persona provisioned in `apfrd.onmicrosoft.com` on 2026-05-20 is exercised end-to-end: `admin`, `directeur-bordeaux`, `directeur-complexe`, `rh-aquitaine`, `rh-siege`, `collaborateur-simple`, `tresorier-bordeaux`, `dpo`, `it`, `benevole-aquitaine`, `chef-equipe-bordeaux`, `chef-service-bordeaux`, `directeur-territorial-aquitaine`, `juriste-siege`, `rssi`, `communication-siege`, `elu-ca-national`, `president-cd-aquitaine`, `secretaire-cd-aquitaine`. A coverage assertion confirms the personas cover all 4 privileges + 23 of 24 functional roles (the `partenaire` gap is intentional per ADR-0025). **Infra + docs** | File | Change | | --- | --- | | `infra/test-tenant.entra.example.json` | Template GUID → slug map (24 entries, full v1 catalogue). Real file (`*-tenant.entra.json`) gitignored. | | `infra/README.md` | New row + "Entra group map" section documenting the load semantics + provisioning workflow. | | `apps/portal-bff/.env.example` | New `ENTRA_GROUP_MAP_PATH` block. | | `.gitignore` | `infra/*-tenant.entra.json` (except `.example.json`). | | `tsconfig.base.json` | `shared-auth` path alias. | | `notes/entra-groups-claim-activation.md` | Operator runbook for the Entra admin-centre step (Token configuration → Add groups claim → Security groups → Group ID). | **ADR amendment** | File | Change | | --- | --- | | `docs/decisions/0025-authorization-model-privileges-roles-scopes.md` | Path references updated `libs/feature/auth/...` → `libs/shared/auth/...` (4 occurrences), and the `ENTRA_GROUP_TO_ROLE` static-record example replaced by the actual `EntraGroupToRoleResolver` + JSON shape. | ## Notes for the reviewer - **Why `libs/shared/auth/` and not `libs/feature/auth/` (as the ADR initially said).** The existing `libs/feature/auth/` carries `tags: ['scope:portal-shell', 'type:feature']`, which the Nx `enforce-module-boundaries` rule in `eslint.config.mjs:36-46` blocks the BFF from importing. The catalogue is needed by both sides (BFF builds the `Principal`, SPA will gate UI conditionally later), so a `scope:shared` lib is the correct home. The ADR is patched in the same PR so the document and the code agree. - **Why drop unknown privileges with a WARN instead of preserving them.** `Portal.GhostRole` in the `roles` claim is either a leftover from a different app registration or a v1+1 experiment that hasn't been added to the catalogue yet. Both paths should not silently grant access — drop with a WARN so an operator notices, and the drift CI gate (next PR) catches the source-side equivalent before merge. - **Why the principal builder uses a `ScopeResolver` seam now.** Per ADR-0025 §331 the v1 implementation returns `unrestricted` for everyone; the real version queries the `user_scopes` Prisma table that lands with ADR-0026. The seam is here so the next PR replaces only the implementation, not the call-site in `PrincipalBuilder` or its 24 tests. Per-persona stub scope data was deliberately not embedded in this PR — no guard consumes scopes yet, so it would be write-only documentation that drifts from the eventual Prisma seed. - **Why `user.id` and `user.personId` placeholder to `entraOid`.** Same logic: the real UUIDs land with the `Person` + `User` Prisma schema (proposed ADR-0026). Until then, populating both fields with `entraOid` lets consumers key on `principal.user.id` today and get a real value later without branching on availability. The seam is one place (in the builder), not 24 guards. - **Why session.types.ts carries both `user` and `principal`.** Additive instead of replacement: the audit module + the AI-bridge controller key on `user.oid` / `user.tid` directly, and migrating them in the same PR would balloon the diff for zero behavioural benefit. New consumers (`@RequirePrivilege` / `@RequireRole` / `@RequireScope` decorators, next PR) reach for `principal`. - **Map file fails soft on absence, hard on malformation.** Path unset OR file missing → empty resolver + WARN. Sign-in still succeeds, every user gets `roles: []`. This is deliberately fail-soft so a fresh dev environment isn't blocked by an Entra-side dependency. **Bad JSON / unknown slug / duplicate GUID → throws at boot.** The alternative would silently mis-resolve a role. - **Why `boot-time` validation only (no per-request)** for the map file. Catalogue drift is detected once at boot; per-request validation would re-read the file from disk on every sign-in. The trade-off is that a hot-edit to the file requires a BFF restart — acceptable because the file changes once per tenant lifecycle, not per session. - **Entra `groups` claim must be activated** on the BFF's app registration before this code does anything useful. The runbook in `notes/entra-groups-claim-activation.md` walks through the steps; if the claim isn't activated, every user signs in with `principal.roles = []` and a future warn for every group GUID that would have arrived (none, in that case). - **No drift gate yet.** ADR-0025's §"Confirmation" §346 calls for an ESLint custom rule (or `pnpm run` script) that greps every `@RequireRole('...')` literal and asserts membership in the catalogue. That's the third PR in the phasing — there's no `@RequireRole` consumer in the tree yet, so the gate would have nothing to assert against today. ## Test plan - [x] `pnpm nx affected -t lint test build --base=main` — 13 projects green - 497 BFF tests (was 465 — added 4 new `groups`-claim tests in `auth.service.spec.ts`, 1 new test in `session-establisher.service.spec.ts`, 6 new tests in `load-entra-group-map.spec.ts`, 24 new tests in `principal-builder.spec.ts`) - 17 `shared-auth` tests (catalogue counts + resolver contracts) - 1 `shared-util` test (unchanged) - full lint + full build - [x] `pnpm nx format:check` — clean after `pnpm nx format:write` - [x] Manual: `tsc --build libs/shared/auth/tsconfig.lib.json` emits `.d.ts` files at the path `portal-bff`'s webpack expects. - [ ] **Review focus** — the closed catalogues (`PRIVILEGES`, `FUNCTIONAL_ROLES`, `SCOPE_KINDS`) verbatim match ADR-0025; the 19 personas verbatim match `notes/test-tenant-role-assignments.md`; the `Principal` shape matches the ADR §"Principal shape" (modulo the `user.id` / `personId` placeholder); fail-soft on missing map file, hard on malformed. - [ ] **After merge — operator step** : activate the Entra `groups` claim per `notes/entra-groups-claim-activation.md` and drop the real GUIDs into `infra/test-tenant.entra.json` (gitignored). Then a sign-in with `admin@apfrd.onmicrosoft.com` should land `principal.privileges = ['Portal.Admin']` and `principal.roles = ['collaborateur', 'rh']` in Redis. ## What's next Per ADR-0025 §"More Information" phasing: 1. ✅ **This PR** — types + Principal builder + group-to-role mapping skeleton. 2. **Next PR** — `@RequirePrivilege` + `@RequireRole` + `@RequireScope` decorators + guard integration tests against the 19 personas. 3. **PR after that** — drift CI gate (ESLint custom rule asserting every `@RequireX('...')` literal is in the catalogue). 4. **Depends on ADR-0026** — `user_scopes` Prisma table + seed + `PrismaScopeResolver` replacing `StubScopeResolver`. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #206 |
||
|
|
c9a1e195fe |
docs(adr-0025): promote to accepted + sync persona matrix with test tenant (#205)
## Summary ADR-0025 was merged as `proposed` in #201. The test tenant (`apfrd.onmicrosoft.com`) has since been provisioned with the full role / user matrix — 4 privileges + 24 security groups + 19 users with all assignments per the persona table. The ADR is now the implementation reference, so this PR: 1. Promotes the ADR from `proposed` to `accepted`. 2. Syncs the document with what is actually in place — the privilege catalogue grows from 1 to 4 entries (the previously "anticipated future" privileges that the test tenant already has), and the persona matrix grows from 10 to 19 entries (so every one of the 24 functional-role groups has at least one member, closing the gap that prompted `notes/test-tenant-role-assignments.md` and `notes/entra-group-members.md`). 3. Records the Entra app-role GUIDs in a new "Provisioned in the test tenant" subsection for traceability — the GUIDs are stable IDs the implementation will need. 4. Updates the index + the `CLAUDE.md` roll-up. No code changes. No implementation skeleton — that lands in the next PR (proposed: `feat(libs/feature/auth): authorization types + Principal builder skeleton`). ## What lands | File | Change | |---|---| | `docs/decisions/0025-authorization-model-privileges-roles-scopes.md` | Frontmatter `status: proposed → accepted`; privilege catalogue extended from 1 to 4 entries; persona matrix rewritten from 10 to 19 entries; new `Provisioned in the test tenant (2026-05-20)` subsection capturing the four app-role GUIDs. | | `docs/decisions/README.md` | 0025 row: `proposed → accepted`. | | `CLAUDE.md` | Roll-up `0001 → 0024 accepted` → `0001 → 0025 accepted`; Architecture list grows an "Authorization model" bullet. | The two operator-facing notes files (`notes/test-tenant-role-assignments.md`, `notes/entra-group-members.md`) are gitignored and unchanged — they served their purpose during tenant provisioning and remain as runbooks. ## Notes for the reviewer - **Why promote now rather than couple with the first implementation PR (the pattern from #194 → #195 → #196).** The Entra-side provisioning *is* the implementation for this ADR — the next PR is a portal-side reflection of decisions that are already concrete in the tenant. Promoting now keeps the document honest about what the test tenant runs against. - **Why all 4 privileges enter the v1 catalogue.** The ADR originally shipped `Portal.Admin` as the sole v1 entry and listed the other three under "anticipated near-future entries". The test tenant has all four; the catalogue should match. The three new entries are explicitly marked "provisioned; consumer surface deferred" so a reader does not look for non-existent surfaces. - **Why 19 personas, not the cleaner 24 (one per role).** Several APF jobs genuinely combine multiple roles (RH siège often handles paie + compta; DPO often wears the quality officer hat; local delegates often grow out of volunteer roles). Densifying these existing personas is more faithful to real APF org structure than inventing 14 single-role test users. Distinct personas were created where the *scenario* is distinct (scope variations, governance positions) — see the matrix in the ADR for the breakdown. - **Why `apf-role-partenaire` stays empty in v1.** Placeholder per the original ADR; no consuming surface to test against. The group exists in Entra so the schema is locked, but a user assignment without a guard to exercise would be theatre. The first partner-facing feature adds the user. - **GUIDs in the ADR.** The four app-role GUIDs are repo-stable identifiers; recording them in the ADR keeps the document self-sufficient when a future contributor opens it without access to the Entra portal. The 24 functional-role group GUIDs are tenant-specific and stay in a gitignored `infra/test-tenant.entra.json` once the implementation PR creates it — referenced by name only in `libs/feature/auth/src/lib/entra-group-to-role.ts`. - **No `prettier --write` damage.** The persona matrix is a wide table; Prettier sometimes reflows wide markdown tables. The diff is clean — Prettier left the table intact on this run. ## Test plan - [x] `prettier --check docs/decisions/0025-authorization-model-privileges-roles-scopes.md` — passes (hook ran on commit). - [x] Markdown links inside the ADR still resolve (`0020`, references to other ADRs unchanged). - [x] Status row in `docs/decisions/README.md` reflects `accepted`. - [x] `CLAUDE.md` roll-up line + Architecture list updated; no other instances of "0024" needed bumping. - [ ] **Review focus** — the expanded privilege catalogue (4 entries, one of them already had a guard, three new ones documented), the 19-entry persona matrix, the "Provisioned in the test tenant" subsection (especially the GUIDs — make sure none was mistyped from `notes/role-user.txt`). ## What's next With ADR-0025 accepted, the implementation phasing recorded in its `§More Information` opens: 1. **PR — `libs/feature/auth` extension** : `authorization.types.ts` (catalogue constants for the 4 privileges + 24 functional roles + 6 scope kinds), `entra-group-to-role.ts` (slug map skeleton with placeholder GUIDs ; the operator drops real GUIDs into `infra/test-tenant.entra.json` separately), `Principal` builder hook on the OIDC callback, no new guards yet. 2. **PR — `@RequireRole` + `@RequireScope` decorators + guard tests** : real composition tests against the 19 personas. 3. **PR — drift CI gate** : ESLint custom rule asserting every `@RequireRole('...')` literal in code is in the catalogue. 4. **PR — `prisma/seed.ts` for `user_scopes`** : depends on the `Person` + `User` schema (proposed ADR-0026). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #205 |