Detector spec (from hypatia#333 Pattern 2)
Pattern 2 — Required-check depends on a package only listed in optionalDependencies
Severity: high (contradiction: required check that can't be installed)
Detection (Hypatia.Rules.CicdRules):
- For each workflow job listed in
branches/main/protection.required_status_checks.contexts:
- Scan its
run: steps. If any step does require("X") (or imports X) and X appears in a sibling package.json's optionalDependencies block but not in dependencies or devDependencies, flag it.
- The combination of "required" + "optional install" is a structural contradiction.
Worked example (this session):
affinescript/editors/vscode/out/extension.cjs runtime-requires @hyperpolymath/affine-vscode.
affinescript/editors/vscode/package.json lists it in optionalDependencies (so npm install doesn't 404 when the package is missing).
vscode-smoke was NOT required (correct), but the same pattern in a required check would be silently broken. Was masked here by continue-on-error: true (see Pattern 5).
- Fix: affinescript#380 embedded the adapter source at codegen time so the runtime no longer needs the optional dep at all.
Remediation guidance to emit:
Either move the dep out of optionalDependencies so installs are reliable, or remove the runtime require (embed the source / inline the adapter / use a different lookup path).
Implementation pointers
- Detection algorithm: For each job in
branches/main/protection.required_status_checks.contexts, scan run: steps for require("X")/imports; flag if X is in sibling package.json's optionalDependencies only (not in dependencies/devDependencies).
- Real-world example:
affinescript/editors/vscode/out/extension.cjs runtime-requires @hyperpolymath/affine-vscode while package.json lists it in optionalDependencies only. Masked by continue-on-error: true; would have silently broken any required check.
- Landed fix (reference): affinescript#380 (embedded adapter source at codegen time, removing the runtime require).
- Rule statement: Either move the dep out of
optionalDependencies so installs are reliable, or remove the runtime require (embed the source / inline the adapter / use a different lookup path).
Acceptance
Source cohort: hypatia#333.
Detector spec (from hypatia#333 Pattern 2)
Pattern 2 — Required-check depends on a package only listed in
optionalDependenciesSeverity: high (contradiction: required check that can't be installed)
Detection (
Hypatia.Rules.CicdRules):branches/main/protection.required_status_checks.contexts:run:steps. If any step doesrequire("X")(or imports X) and X appears in a siblingpackage.json'soptionalDependenciesblock but not independenciesordevDependencies, flag it.Worked example (this session):
affinescript/editors/vscode/out/extension.cjsruntime-requires@hyperpolymath/affine-vscode.affinescript/editors/vscode/package.jsonlists it inoptionalDependencies(sonpm installdoesn't 404 when the package is missing).vscode-smokewas NOT required (correct), but the same pattern in a required check would be silently broken. Was masked here bycontinue-on-error: true(see Pattern 5).Remediation guidance to emit:
Implementation pointers
branches/main/protection.required_status_checks.contexts, scanrun:steps forrequire("X")/imports; flag if X is in siblingpackage.json'soptionalDependenciesonly (not independencies/devDependencies).affinescript/editors/vscode/out/extension.cjsruntime-requires@hyperpolymath/affine-vscodewhilepackage.jsonlists it inoptionalDependenciesonly. Masked bycontinue-on-error: true; would have silently broken any required check.optionalDependenciesso installs are reliable, or remove the runtime require (embed the source / inline the adapter / use a different lookup path).Acceptance
lib/rules/<name>.exif Elixir, or matching the repo's rule DSL)Source cohort: hypatia#333.