-
Notifications
You must be signed in to change notification settings - Fork 1
Dependency Policy
This page explains how dependencies enter, update, and are constrained in openrunic, and the reasoning behind each rule. It is for anyone reviewing a Dependabot pull request or adding a package.
Ask three questions before adding anything.
Does a platform primitive already do this? The API generates its OpenAPI document from Zod's native JSON Schema output rather than adding a wrapper library, on the grounds that OpenAPI 3.1's schema object already is JSON Schema 2020-12. The component library uses a native <select> and a native <dialog> rather than headless-component packages.
Is its licence on the allow list? .grant.yaml is deny-by-default. See Security and supply chain.
What is its transitive weight? The SBOM covers the whole tree, and grype scans all of it. A small package with a large dependency graph is not a small package.
Two settings in pnpm-workspace.yaml apply to everything.
minimumReleaseAge: 4320pnpm refuses to install any package version published less than three days ago. Most compromised-release incidents are caught and yanked within hours; a three-day quarantine turns the fastest-moving class of supply-chain attack into a non-event for this repository, at the cost of never being first to a new patch.
onlyBuiltDependencies:
- '@prisma/client'
- '@prisma/engines'
- prisma
- esbuild
- sharp
- unrs-resolverpnpm 10 blocks dependency postinstall scripts by default. Only these six may run. A postinstall script executes arbitrary code on every developer machine and every CI runner, so the allow list is short and each entry is there because the package genuinely cannot work without it.
Configured in .github/dependabot.yml, weekly on Monday, targeting dev, with a limit of ten open pull requests per ecosystem. npm uses the chore(repo) prefix and GitHub Actions uses ci(repo), because the PR-title gate requires a scope from the allowed list and deps is not one.
Dependabot reads this file from the default branch (
main), not fromdev.target-branchonly aims the pull requests. A change here sits inert until it is promoted, so keep the two copies byte-identical.
groups:
typescript-eslint:
patterns: ['typescript', '@types/*', 'eslint*', '@typescript-eslint/*']
update-types: [minor, patch]
testing:
patterns: ['vitest*', '@vitest/*', '@testing-library/*']
update-types: [minor, patch]
build-tooling:
patterns: ['turbo', 'prettier', 'husky', 'lint-staged']
update-types: [minor, patch]
minor-and-patch:
patterns: ['*']
update-types: [minor, patch]Two rules are doing real work here, and both are the kind of thing you only learn by being bitten.
Every group declares update-types, including the named ones. A named group without it silently swallows majors too. That is how a breaking major bump ends up inside an unreviewable group pull request instead of the individual pull request a breaking major needs. Majors always get their own pull request and their own review.
The catch-all group must stay last. A dependency joins the first group it matches, so an earlier * pattern would swallow every named group above it.
The GitHub Actions ecosystem groups minor and patch only for the same reason: action majors are routinely breaking, so each lands alone.
Every ignore entry needs a prose rationale and a revisit condition. There is exactly one today, on TypeScript majors: the compiler rewrite ships without a JavaScript compiler API that the current lint tooling peer-caps against, so majors are blocked while minors and patches still flow. The entry records the condition under which it is removed.
pnpm-workspace.yaml carries exact-version overrides for transitive dependencies that cannot be fixed in range:
overrides:
postcss: 8.5.26
sharp: 0.35.1
esbuild: 0.28.2Two conventions here matter.
Exact versions, never ranges. A range key silently stops matching when the parent changes its own pin, and an override that stops applying leaves you believing a vulnerability is fixed when it is not.
Every override carries its reason and its re-check condition in a comment. The first two are advisory fixes where the parent framework pins a lower version exactly. The third is subtler and worth knowing about: the bundler ships a prebuilt binary, so its embedded standard library is what vulnerability scanners see, and the old line embeds a version with a critical finding. It is pulled in transitively by a build-tooling dependency, so the override is the only route to it.
- Read the changelog. Green CI on a dependency bump means the suite still passes, not that behaviour is unchanged. A green tick is not a review.
- Check whether the group swallowed a major. If it did, the config is wrong; fix the config rather than merging the group.
- Check the lockfile diff, not only the manifest. A patch bump on a direct dependency can move a large transitive subtree.
- Confirm the supply-chain gate ran. Dependabot runs with its own secret store, so the Sonar stage is skipped for its pull requests, and grype's SARIF upload is skipped for Dependabot-actor runs. The job's own exit code still gates.
- Prefer a small revert to a fast fix. These are dependency changes, not feature work.
| Thing | Pinned where |
|---|---|
| Node |
.nvmrc and engines in package.json, with engine-strict=true
|
| pnpm |
packageManager in package.json; CI reads it rather than declaring its own |
| GitHub Actions | Full commit SHA in every uses:, version in a trailing comment |
| CLI security tools | Version plus SHA-256 in the workflow, verified before extraction |
| PostgreSQL in CI |
postgres:17-alpine, with a note to pin it to production's major once one exists |
openrunic is an open-source operating system for human health. Pre-alpha: do not run it in production, and never put real patient data into it.
Repository · Licence (AGPL-3.0-only) · Security policy · Contributing · Code of conduct
Where this wiki and the repository disagree, the repository is right.