fix(ci): audit the whole call tree in the no-caching gate - #861
Conversation
`scripts/lint-no-workflow-caching.mjs` keeps the GitHub Actions cache out of the two credential-bearing workflows, because a poisoned cache entry would execute in a job that can publish to npm. It had three fail-open holes, each of which printed `OK`. **1. It stopped at a local composite action.** The gate read a step's own `uses:` and went no further, so a workflow could reach `actions/cache` through one indirection — `uses: ./.github/actions/x` — and stay green. Reproduced against a copy of release.yml with a cache-restoring composite spliced in: exit 0, no output, while the composite it never opened restored two caches. It now flattens local composites and checks every step inside them, naming the whole trail so the report points at the file the cache is actually in. **2. It skipped a job that delegates to a reusable workflow.** Such a job has no `steps:` at all — it is `jobs.<id>.uses` — so the walker was handed an empty list and skipped the job entire. Confirmed before the fix against a caller whose only job was `uses: ./.github/workflows/reusable.yml` with `secrets: inherit`, the called workflow holding `actions/cache@v4`: exit 0, `OK`, nothing scanned. The verdict deliberately ignores `secrets:` — `permissions:` is inherited independently and is what mints the OIDC token npm trusted publishing signs with, so a call passing no secrets can still publish. **3. A third-party cache action was invisible.** The rules only recognised an action literally named `actions/cache*` and one taking a `cache:` input, so `useblacksmith/cache@v5` and `Swatinem/rust-cache@v2` both passed. The repair is an inversion rather than a longer denylist: every REMOTE `uses:` reachable from a targeted workflow must now appear in an `AUDITED_ACTIONS` allowlist, so an action this gate has never seen is a finding by default whatever it is called. A denylist fails open on the action nobody has met yet — silently correct until the day it is silently wrong, and wrong in the direction that prints `OK`. It also cannot cover the class most likely to be added by accident: a `setup-<tool>` action that caches BY DEFAULT, with no `cache:` input to inspect and no "cache" in its name. The allowlist cannot go stale silently, which is why it was chosen: its staleness is a build failure naming the exact action and the file it was added to, so the person adding it is the person told to audit it, in the same PR. Cost was measured rather than assumed — the two targeted workflows reach four actions between them. Also adds the missing premise assertion to `integration-workflow-paths`: its requirement set is DERIVED from `@/`-aliased imports, so "no suite uses that alias" and "every import is covered" were the same green. Mutation-tested — rewriting the suites onto the public entry empties the set, and the check then passed with `packages/stack/src/dynamodb/**` deleted from the filter, which is verbatim the #815 gap the file exists to prevent. Second of four stacked PRs splitting the protect-ffi absorption. Independent of the vendoring: these are pre-existing holes in a control that already shipped.
🦋 Changeset detectedLatest commit: 7f819fb The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR adds recursive cache and action auditing for local composite actions and reusable workflows. It adds workflow fixtures and tests for nested, cyclic, unresolved, remote, third-party, malformed, and unaudited references. It also updates release guidance and a setup action version. ChangesWorkflow audit
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Workflow
participant LintScript
participant LocalAction
participant ReusableWorkflow
Workflow->>LintScript: process jobs and uses references
LintScript->>LocalAction: resolve and inspect local composite action
LocalAction-->>LintScript: return nested steps
LintScript->>ReusableWorkflow: resolve and inspect local workflow call
ReusableWorkflow-->>LintScript: return nested jobs
LintScript-->>Workflow: report findings and exit status
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
freshtonic
left a comment
There was a problem hiding this comment.
Verdict: Approve. Verified locally: pnpm run test:scripts 295/295 pass, pnpm run lint:workflow-cache exits 0 on the real release.yml / tests-supply-chain.yml, and biome check on the three changed source files is clean. The core traversal is correct across every shape the fixtures exercise, the fail-closed allowlist is the right design, and the changeset + stash-supply-chain-security skill are both updated as AGENTS.md requires. No blocking issues.
Non-blocking
1. with.cache false-positive on a local-composite invocation. checkStep runs the with.cache: <truthy> rule on every step, including a step whose uses: is a local composite. For a composite, with: is arbitrary inputs — an input named cache is unrelated to the Actions cache. Reproduced against a composite with a cache input invoked as with: { cache: true }:
job "release" step "Call composite with cache input": `with.cache: true` restores the GitHub Actions cache
This is the same false-positive class you deliberately avoid one level up for reusable workflows — job-level with: is kept out of checkStep, and reusable-input-named-cache.yml pins that. It's currently inert (no target workflow reaches a composite) and errs in the fail-closed direction, so not blocking — but consider skipping the with.cache rule when the step is a local-composite uses:, or a one-line comment noting the asymmetry is intentional.
2. Exit-2 suppresses cache offenders on a mixed run. unresolved is printed and process.exit(2) fires before the offenders (exit 1) block. A run that collects both an un-auditable reference and a real cache offender prints only the former. Both fail CI, so it's cosmetic, but the actionable cache finding stays hidden until the path is fixed. Consider printing both lists before exiting with the higher code.
3. Minor scope. The pnpm/action-setup@v6.0.8 → v6.0.9 bump in .github/actions/integration-setup/action.yml is unrelated to the gate. Harmless, noted only for commit atomicity.
Highlights
- Fail-closed allowlist with a thoroughly documented rationale (why a denylist was rejected; the
setup-<tool>-caches-by-default class with nocache:input and no telling name). - The module-load assertion that no
AUDITED_ACTIONSentry is cache-shaped makes the one careless re-opening edit impossible. - Fixtures are exhaustive and adversarial: composite and reusable cycles (asserted by offender count, not just exit),
action.yamlvs.yml, leading-whitespaceuses:, remote step vs remote reusable workflow,secrets:-agnostic verdict, invalidsteps:+uses:job, and multiple third-party cache vendors. - The
required.size > 0premise assertion added tointegration-workflow-paths.test.mjscloses a genuine "green because it checked nothing" gap, mirroring the #815 fix.
…puts
Two review findings on the call-tree traversal.
**`with.cache` fired on a local composite's declared inputs.** `checkStep`
applied the `with.cache: <truthy>` rule to every step, including one whose
`uses:` is a local action — where `with:` is that action's arbitrary declared
inputs. A composite taking a `cache` input that decides whether to reuse a
binary already in the working tree, invoked `with: {cache: true}`, was reported
as "restores the GitHub Actions cache". It is the step-level twin of the false
positive `walkJob` already refuses to make by never running the step rules over
a job-level `with:`, which `reusable-input-named-cache.yml` pins.
The exemption is keyed on the resolved manifest's `runs.using` being
`composite`, not on the `uses:` starting with `./`, because its justification is
"the body is audited instead" rather than "local is trusted". A local `uses:`
resolving to nothing, or to a JS/Docker action with no step list, keeps the
rule: there the gate opens no step list, and a local `uses:` is already exempt
from AUDITED_ACTIONS, so the caller's `with:` is the only signal left — dropping
it for every local reference would make a two-line `action.yml` a supported way
past the gate. `walkSteps` now resolves the action before checking the step
rather than after, so one reading feeds both decisions and the suppression
cannot outlive the audit that justifies it.
`cache-passthrough` pins the fail-closed half: a composite forwarding its
`cache` input into `actions/setup-node` is still one finding, named on the step
inside the composite rather than on the caller that switched it on.
**Exit 2 suppressed the cache offenders on a mixed run.** The un-auditable list
printed and called `process.exit(2)` before the offender block was reached, so a
run collecting both showed only the reference the gate could not open. The
actionable finding — the one with a step to delete — stayed hidden until the path
was fixed, then arrived on the next run looking new. Both lists now print before
either exit. Exit 2 still outranks 1, but no longer because nothing was found
caching: on a mixed run something was. An incomplete scan is simply the more
severe verdict, since the exit 1 reports what this gate could see and the exit 2
says that list may be short.
Scripts suite 216 passing (49 in the touched file, +5); all three lint gates OK;
biome 0 errors.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
scripts/__tests__/lint-no-workflow-caching.test.mjs (1)
168-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a timeout to the
runhelper so a cycle regression fails instead of hanging.The comment states the risk correctly.
execFileSyncat Line 22 has notimeout, so a regression in thevisitedguard blocks the suite until the CI job limit. A timeout converts that hang into a normal test failure with the existing exit-code assertion.♻️ Proposed change to the shared
runhelper (Lines 20-30)function run(...targets) { try { - execFileSync('node', [SCRIPT, ...targets], { encoding: 'utf8' }) + execFileSync('node', [SCRIPT, ...targets], { + encoding: 'utf8', + timeout: 30_000, + }) return { exitCode: 0, output: '' } } catch (err) { return { exitCode: err.status, output: String(err.stdout) + String(err.stderr), } } }Note: on a timeout
err.statusisnullanderr.signalis set, so the exit-code assertions still fail loudly rather than passing.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/__tests__/lint-no-workflow-caching.test.mjs` around lines 168 - 176, Update the shared run helper around execFileSync to pass a finite timeout option, ensuring cyclic-command regressions fail promptly instead of hanging. Preserve the existing error handling and exit-code assertions so timeout failures remain visible to the test.scripts/lint-no-workflow-caching.mjs (2)
24-27: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
LOCAL_USESaccepts../, but the comment states only./is valid.GitHub resolves a local
uses:only with the./prefix.../is invalid to GitHub. The regex allows it, so a../…reference is treated as local and becomes exempt fromAUDITED_ACTIONS.resolveActionFilethen probes outside the workspace root. Align the pattern with the documented rule.♻️ Restrict the pattern to `./`
-const LOCAL_USES = /^\.{1,2}\// +const LOCAL_USES = /^\.\//🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/lint-no-workflow-caching.mjs` around lines 24 - 27, Update the LOCAL_USES pattern in scripts/lint-no-workflow-caching.mjs to match only the ./ prefix, excluding ../ references. Preserve the surrounding local-action detection and ensure resolveActionFile cannot treat paths outside the workspace as local actions.
256-263: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueApply the same
isFileguard used byresolveWorkflowFile.
resolveWorkflowFilerejects a directory to avoid an unhandledEISDIRatreadFileSync.resolveActionFiledoes not. A directory namedaction.ymltherefore aborts the run at line 317 instead of producing a report. The hazard is the one already documented at lines 344-347.♻️ Add the file-type check
function resolveActionFile(workspaceRoot, usesPath) { const dir = resolve(workspaceRoot, usesPath) for (const name of ['action.yml', 'action.yaml']) { const file = join(dir, name) - if (existsSync(file)) return file + if (existsSync(file) && statSync(file).isFile()) return file } return null }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/lint-no-workflow-caching.mjs` around lines 256 - 263, Update resolveActionFile to apply the same isFile guard used by resolveWorkflowFile before returning a matching action.yml or action.yaml path. Skip directories with those names and return null when no regular action file exists, preventing the later readFileSync path from receiving a directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/lint-no-workflow-caching.mjs`:
- Line 317: Guard all YAML file reads and parses in
scripts/lint-no-workflow-caching.mjs using a shared helper: at lines 317, 399,
and 444, catch failures, record an unresolved entry with the relevant manifest
or workflow path and parse error, and return a null result. At line 317, treat
null as a non-composite manifest so checkStep retains all rules; at lines 399
and 444, treat null as having no jobs and stop that traversal, while the target
workflow path at line 444 must allow scanning subsequent targets.
---
Nitpick comments:
In `@scripts/__tests__/lint-no-workflow-caching.test.mjs`:
- Around line 168-176: Update the shared run helper around execFileSync to pass
a finite timeout option, ensuring cyclic-command regressions fail promptly
instead of hanging. Preserve the existing error handling and exit-code
assertions so timeout failures remain visible to the test.
In `@scripts/lint-no-workflow-caching.mjs`:
- Around line 24-27: Update the LOCAL_USES pattern in
scripts/lint-no-workflow-caching.mjs to match only the ./ prefix, excluding ../
references. Preserve the surrounding local-action detection and ensure
resolveActionFile cannot treat paths outside the workspace as local actions.
- Around line 256-263: Update resolveActionFile to apply the same isFile guard
used by resolveWorkflowFile before returning a matching action.yml or
action.yaml path. Skip directories with those names and return null when no
regular action file exists, preventing the later readFileSync path from
receiving a directory.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ca33f089-0b0f-4da6-8987-682de24de655
📒 Files selected for processing (65)
.changeset/olive-moons-shave.md.github/actions/integration-setup/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/audited-actions.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/cache-family.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/cache-passthrough/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/cachey-restore/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/cachey-save/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/cachey/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/clean-composite/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/input-named-cache/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/js-action/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/loop-a/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/loop-b/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/missing-explicit-false/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/outer/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/setup-node-cache/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/thirdparty-cache/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/actions/yaml-ext/action.yamlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-cache-passthrough.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-cache-restore.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-cache-save.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-clean.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-cyclic.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-input-named-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-leading-space.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-missing-explicit-false.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-nested.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-setup-node-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-thirdparty-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-unresolvable-with-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-unresolvable.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/composite-yaml-ext.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/local-js-action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/mixed-unresolved-and-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/composites/.github/workflows/third-party-uses.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/actions/cachey/action.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-clean.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-composite.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-input-named-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-loop-a.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-loop-b.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-missing-explicit-false.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-outer.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/called-thirdparty-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-both.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-clean.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-composite.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-cyclic.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-explicit-secrets.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-input-named-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-missing-explicit-false.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-nested.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-no-secrets.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-remote.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-thirdparty-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/reusable/.github/workflows/reusable-unresolvable.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/thirdparty-cache.ymlscripts/__tests__/fixtures/lint-no-workflow-caching/unaudited-setup.ymlscripts/__tests__/integration-workflow-paths.test.mjsscripts/__tests__/lint-no-workflow-caching.test.mjsscripts/lint-no-workflow-caching.mjsskills/stash-supply-chain-security/SKILL.md
…crashing
Three review findings, all in the same class: a file this gate could not read
took the run down with it rather than being reported.
**Unguarded `yaml.load`.** All three read sites — a composite manifest, a called
workflow, and the target workflow itself — threw straight out of the run on
anything unparseable: a stack trace instead of a finding, exit 1 which is
indistinguishable from "found a caching issue", and every remaining target never
scanned. A malformed `release.yml` meant `tests-supply-chain.yml` was not looked
at either. One shared `loadYaml` helper now reports and returns null, and a file
that will not parse lands in the un-auditable list for the reason that list
exists: it hands the traversal no step list, so nothing below it is audited.
Only the first line of the error is kept — js-yaml puts the position there and
follows it with a source snippet whose own indentation would wreck the report's
bullets. Each caller decides what null means: `walkSteps` reads it as a
NON-composite manifest, so `checkStep` keeps every rule including `with.cache`,
and the target loop continues to the remaining targets.
**`LOCAL_USES` accepted `../`, which GitHub does not.** The comment above it
already said "GitHub requires the `./` prefix"; the regex said `{1,2}`. That was
not a harmless widening, because "local" means two things here — exempt from
AUDITED_ACTIONS, and handed to a resolver that `resolve()`s the value against
the workspace root. Confirmed by dropping a workflow holding `actions/cache@v4`
one directory above a fixture root: `uses: ../outside-workflow.yml` opened it,
audited it, and printed the finding with a `../` trail. A file outside the
checkout, read as though it were inside. `../` now gets its own verdict rather
than falling through to the remote branches, which would be fail-closed but
would tell the reader to audit a published action that does not exist.
**`resolveActionFile` lacked the `isFile` guard its sibling has.** A DIRECTORY
named `action.yml` passed a bare `existsSync` and reached `readFileSync` as the
manifest — unhandled EISDIR. `resolveWorkflowFile` has guarded exactly this
since it was written, with a comment explaining why; the asymmetry was the bug.
Guarded, the directory is skipped and the existing "no action.yml or action.yaml
there" report is what comes out.
One further suggestion was checked and not taken: adding a `timeout` to the
test helper's `execFileSync`, on the premise that a regression in cycle
protection would hang the suite. Deleting the `visited` guard and running the
cyclic fixture blows the stack and exits in well under a second — the script is
synchronous end to end and has no way to hang. The comment claiming otherwise
was the source of the suggestion and is corrected instead.
Scripts suite 224 passing (+8); all three lint gates OK; biome 0 errors.
Stack 2 of 4 — splitting #858. Base: #860.
packages/protect-ffi(subtree, upstream history preserved)What
scripts/lint-no-workflow-caching.mjskeeps the GitHub Actions cache out of the two credential-bearing workflows, because a poisoned cache entry would execute in a job that can publish to npm. It had three fail-open holes, each of which printedOK:release.ymlwith a cache-restoring composite spliced in: exit 0, no output, while the composite it never opened restored two caches.steps:at all, so the walker got an empty list and skipped the job entire. Confirmed before the fix: exit 0,OK, nothing scanned. The verdict ignoressecrets:deliberately —permissions:is inherited independently and is what mints the OIDC token npm trusted publishing signs with.useblacksmith/cache@v5andSwatinem/rust-cache@v2both passed. Fixed by inverting to an allowlist rather than lengthening a denylist: a denylist fails open on the action nobody has met yet, and cannot cover the class most likely to be added by accident — asetup-<tool>action that caches by default, with nocache:input and no telling name.Also adds the missing premise assertion to
integration-workflow-paths: its requirement set is derived from@/-aliased imports, so "no suite uses that alias" and "every import is covered" were the same green. Mutation-tested — rewriting the suites onto the public entry empties the set, and the check then passed withpackages/stack/src/dynamodb/**deleted from the filter, verbatim the #815 gap.Why it is separable
These are pre-existing holes in a control that already shipped. Nothing here depends on the vendoring.
Verification
Scripts suite 211 passing (51 in the two touched files); all three lint gates OK; biome 0 errors.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation