Skip to content

fix(npmprofile): Support pnpm settings-only root packages - #80

Merged
yunseo-kim merged 8 commits into
mainfrom
fix/n02-pnpm-settings-only
Aug 12, 2026
Merged

yunseo-kim merged 8 commits into
mainfrom
fix/n02-pnpm-settings-only

Conversation

@yunseo-kim

@yunseo-kim yunseo-kim commented Aug 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • What changed?
    • Treat a parsed pnpm-workspace.yaml object without packages as a terminal standalone-root package boundary with zero workspace patterns.
    • Keep present non-array/non-string packages values and unsupported patterns fail-closed.
    • Add the ADR 0078 accepted and rejected fixture matrix and register it with the F06 harness.
  • Why is this needed?
    • The first vers-js dogfood run 31622651874 exposed that valid modern pnpm policy settings were incorrectly classified as malformed workspace metadata.

Related Issues

Issue #22 remains open for the orchestrator's post-merge dogfood retry.

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Test/CI
  • Breaking change
  • Other:

Changelog

  • Category: Fixed
  • User-facing note: Standalone pnpm root packages with settings-only pnpm-workspace.yaml files now resolve successfully without weakening malformed-workspace rejection.

Changelog update:

  • CHANGELOG.md [Unreleased] updated
  • Not needed because this change is not user-facing

Checklist

General

  • PR title follows Conventional Commits format: type(scope): Summary
  • This PR does not expose backend/internal implementation details in a public repo.
  • No secrets, tokens, keys, or private endpoints are included.
  • Changes stay within this repository's intended scope.

CI/Workflow Changes (if applicable)

Not applicable; no workflow files changed.

  • All uses: references are pinned to full 40-character commit SHAs (with # vX.Y.Z comment)
  • step-security/harden-runner is included as the first step in every job
  • Job-level permissions are used instead of top-level permissions

Protocol / Compatibility Impact

  • No protocol/spec impact
  • Protocol/spec updated
  • Conformance tests updated
  • Breaking change is versioned and migration notes are included

If impacted, describe compatibility impact:

ADR 0078 and both architecture specs were already synchronized by #79. This implementation admits only the documented root-only pnpm shape; existing npm/Yarn workspaces and pnpm arrays remain unchanged.

Testing

  • Unit tests added/updated
  • Integration or conformance tests added/updated
  • Tests pass
  • Lint and format pass
  • Type check passes
  • Manual verification performed

Describe test evidence:

  • RED: go test ./internal/npmprofile -run 'TestWorkspaceDiscovery|TestLockfileRules' -count=1 failed because pnpm settings-only root emitted windlass.verify.error.package-resolution-invalid.
  • GREEN: go test ./internal/npmprofile -count=1 passed.
  • Fixture harness: go run ./cmd/slsa-builder-internal fixture-check --index testdata/fixtures/index.json --surface npm --phase build-pack returned pass/exit 0.
  • Full gates: go test -race ./..., go vet ./..., golangci-lint run ./... (0 issues), and golangci-lint fmt --diff (clean) passed.

Fixture matrix:

Fixture Case Expected
pnpm-settings-only-root-valid Settings-only root, omitted packages Pass; root package and root manager directory
pnpm-workspace-packages-wrong-type Present scalar packages Fail with windlass.verify.error.package-resolution-invalid
pnpm-settings-only-subdirectory Subdirectory beneath settings-only root Fail with windlass.verify.error.package-resolution-invalid

Security review: no new path or pattern input is accepted. The relaxed branch is reachable only after strict YAML object parsing and only when packages is absent; it accepts exact candidate-root equality and rejects subdirectories immediately, so farther ancestor discovery and standalone subpackage fallback cannot weaken one-package or ambiguity checks.

Documentation

  • README updated
  • Spec/docs updated
  • Changelog decision completed above

Specs were already updated by #79; no ADR/spec files changed here.

Rollout / Risk

  • Risk level: Low
  • Rollback plan: Revert the implementation and additive fixture commits; no persisted data or remote mutation behavior changes.

Reviewer Checklist

  • Scope is clear and minimal
  • Security and boundary checks passed
  • Tests and docs are sufficient
  • Compatibility impact is correctly handled

Treat an omitted packages member as a terminal root-only package boundary while preserving fail-closed subdirectory selection.

Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
Signed-off-by: Yunseo Kim <git@yunseo.kim>
@@ -0,0 +1,3 @@
packages: packages/*
minimumReleaseAge: 1440

@semgrep-code-windlasstech semgrep-code-windlasstech Bot Aug 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pnpm workspace configuration does not set a minimum release age. Newly published packages can be malicious or unstable. Add minimumReleaseAge: 10080 (minutes) to wait at least seven days before installing newly published package versions. Added in: v10.16.0 Reference: https://pnpm.io/settings#minimumreleaseage

🌟 Removed in commit 8d4245e 🌟

@@ -0,0 +1,6 @@
minimumReleaseAge: 1440

@semgrep-code-windlasstech semgrep-code-windlasstech Bot Aug 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pnpm workspace configuration does not set a minimum release age. Newly published packages can be malicious or unstable. Add minimumReleaseAge: 10080 (minutes) to wait at least seven days before installing newly published package versions. Added in: v10.16.0 Reference: https://pnpm.io/settings#minimumreleaseage

✨ Removed in commit 8d4245e ✨

@@ -0,0 +1,6 @@
minimumReleaseAge: 1440

@semgrep-code-windlasstech semgrep-code-windlasstech Bot Aug 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pnpm workspace configuration does not set a minimum release age. Newly published packages can be malicious or unstable. Add minimumReleaseAge: 10080 (minutes) to wait at least seven days before installing newly published package versions. Added in: v10.16.0 Reference: https://pnpm.io/settings#minimumreleaseage

🥳 Removed in commit 8d4245e 🥳

@@ -0,0 +1,3 @@
packages: packages/*

@semgrep-code-windlasstech semgrep-code-windlasstech Bot Aug 12, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing or incorrect blockExoticSubdeps. Set blockExoticSubdeps: true to transitive dependencies from being installed from untrusted sources. Added in: v10.26.0 Reference: https://pnpm.io/settings#blockexoticsubdeps

🎈 Removed in commit 8d4245e 🎈

Signed-off-by: Yunseo Kim <git@yunseo.kim>
@yunseo-kim
yunseo-kim merged commit f7a1644 into main Aug 12, 2026
17 checks passed
@yunseo-kim
yunseo-kim deleted the fix/n02-pnpm-settings-only branch August 12, 2026 18:17
yunseo-kim added a commit that referenced this pull request Aug 13, 2026
…elease retries (#86)

## Summary

Implements the optional, tags-only `source-ref` input for the npm
producer workflow (issue #81, ADRs 0079/0080). A caller can now retry a
failed release with a fixed pipeline: dispatch from a ref that carries
the fixed workflow (e.g. `main`) while the built, attested, and
published content remains the signed release tag.

- What changed?
- New optional `workflow_call` input `source-ref` (full
`refs/tags/<name>` only) on `js-ts-npm-package-slsa3.yml`.
- Pre-checkout resolution: the new internal `npm-profile-source-ref`
subcommand validates, resolves, and peels the tag, and the package
checkout is pinned to the resolved commit SHA (fail-closed on
missing/unresolvable/non-commit refs; no tag mutation ever).
- The signed SLSA v1 predicate records the built source identity and,
when `source-ref` is supplied, additionally records the invocation
ref/SHA as signed invocation context (`source.input_ref`,
`source.invocation_ref`, `source.invocation_revision`,
all-present-or-absent, explicit `null` rejected).
- ADR 0080 verification binding: explicit policy binds the signed
built-source fields; certificate source claims bind the signed
invocation record, with exact single-identity fallback when the input is
omitted.
- Signing consumes the built release identity through the
digest-verified handoff (build job outputs), while the certificate
context remains bound to the invocation `GITHUB_REF`/`GITHUB_SHA`.
- Release concurrency groups canonicalized to the full-ref form `${{
inputs.source-ref || github.ref }}` across build/sign/publish and
enforced by workflowcheck; the architecture specs were updated
accordingly (a tag name cannot be derived from a full ref in GitHub
expressions).
- Exact-spelling rule: omitted means exactly the empty string;
whitespace-only or padded spellings are rejected with
`windlass.verify.error.source-ref-invalid` because the raw input
participates in job-level concurrency group computation and cannot be
normalized there.
- Why is this needed?
- Today the build job always checks out the caller's invocation ref, so
a `workflow_dispatch` retry either cannot run with a fixed pipeline or
would weaken the provenance claim to an unsigned movable branch head
(real trigger: vers-js v0.1.2 run failure fixed in #80).
- How to test: `go test ./...`, `go test -race ./...`, `go run
./cmd/slsa-builder-internal fixture-check --index
testdata/fixtures/index.json --surface npm --phase provenance-inputs`,
`go run ./cmd/slsa-builder-internal workflow-check --workflow
.github/workflows/js-ts-npm-package-slsa3.yml --profile npm-only`,
`actionlint .github/workflows/js-ts-npm-package-slsa3.yml`.

## Related Issues

- Related #81 (kept open until the post-merge live dogfood retry
confirms the path end-to-end)
- Related #80

## Change Type

- [x] Feature

## Changelog

- Category: Added
- User-facing note: Adds the optional tags-only `source-ref` input to
the npm producer workflow for fixed-pipeline release retries, with
built-source provenance, signed invocation context, and ADR 0080
verification binding.

Changelog update:

- [x] `CHANGELOG.md` `[Unreleased]` updated

## Checklist

### General

- [x] PR title follows Conventional Commits format: `type(scope):
Summary`
- [x] This PR does not expose backend/internal implementation details in
a public repo.
- [x] No secrets, tokens, keys, or private endpoints are included.
- [x] Changes stay within this repository's intended scope.

### CI/Workflow Changes (if applicable)

- [x] All `uses:` references are pinned to full 40-character commit SHAs
(with `# vX.Y.Z` comment) — no new or changed action references
- [x] `step-security/harden-runner` is included as the first step in
every job — unchanged
- [x] Job-level `permissions` are used instead of top-level
`permissions` — unchanged

### Protocol / Compatibility Impact

- [ ] No protocol/spec impact
- [x] Protocol/spec updated
- [x] Conformance tests updated
- [ ] Breaking change is versioned and migration notes are included

Compatibility impact: additive and backward compatible. The input is
optional; when omitted, build metadata, predicate bytes, and
verification outcomes are byte-compatible with the pre-existing contract
(golden-tested). No accepted ADR body was edited; architecture specs
were updated in this PR (concurrency-key canonical form and the
exact-spelling rule).

## Testing

- [x] Unit tests added/updated
- [x] Integration or conformance tests added/updated
- [x] Tests pass
- [x] Lint and format pass
- [x] Manual verification performed

Test evidence:

- Every behavioral commit was developed test-first: new failing tests
were captured in a detached worktree from `main` before each
implementation landed, and each commit is independently green.
- `go test ./...` and `go test -race ./...` pass (all packages);
`golangci-lint run ./...` reports 0 issues; `actionlint` is clean.
- Fixture conformance: 2 accepted + 9 rejected fixtures registered under
the `provenance-inputs` phase (`fixture-check` passes; rejected fixtures
carry their canonical primary diagnostics).
- Workflow static conformance: `workflow-check` passes for `--job
build`, `--job provenance-sign`, `--job publish`, and `--profile
npm-only`, including the nine-input contract, the canonical concurrency
groups, the resolver-before-checkout ordering, and the SHA-pinned
package checkout.
- Manual CLI verification covered: lightweight/annotated tag resolution
against local git remotes, missing/non-commit tag rejection,
whitespace-only/padded rejection, exact-empty passthrough without remote
access, exit-code split (policy failure 1 vs invocation failure 2), and
dispatcher-level sign mismatch classification.
- A five-lane independent review (goal/constraints, hands-on QA, code
quality, security, context mining) iterated to an unconditional pass;
its findings (sign-job identity wiring, input normalization, dispatcher
error classification, concurrency-spelling gap) are fixed in dedicated
commits on this branch.

## Documentation

- [ ] README updated (not needed: both READMEs already document
`source-ref` and the 1-required + 8-optional input contract)
- [x] Spec/docs updated
- [x] Changelog decision completed above

## Rollout / Risk

- Risk level: Medium (touches the release build/sign/publish path; every
new behavior is gated behind the optional input and defaults to
byte-compatible existing behavior)
- Rollback plan: revert this branch; callers that never set `source-ref`
are unaffected. The live vers-js dogfood retry (issue #81 acceptance)
runs only after merge and is recorded separately.

## Reviewer Checklist

- [x] Scope is clear and minimal
- [x] Security and boundary checks passed
- [x] Tests and docs are sufficient
- [x] Compatibility impact is correctly handled

---------

Signed-off-by: Yunseo Kim <git@yunseo.kim>
yunseo-kim added a commit to windlasstech/vers-js that referenced this pull request Aug 13, 2026
## Summary

- Re-pins the slsa-builder reusable workflow in
`.github/workflows/publish.yml` from `4b3edf3` to
`f7a164409a13906effabcab4141f6a0a6ede7374` (`# main, pre-release`).
- Why: the first v0.1.2 dogfood run ([run
31622651874](https://github.com/windlasstech/vers-js/actions/runs/31622651874))
failed in slsa-builder's npm-profile build with
`package-resolution-invalid` (`npm_profile.selection`): this
repository's settings-only `pnpm-workspace.yaml` (no `packages` field)
was not treated as a standalone root package. slsa-builder fixed the
selection logic in windlasstech/slsa-builder#80.
- How to test: retry the v0.1.2 publication after merge; npm has no
`0.1.2` (registry `latest` is still `0.1.1`), so the publish convergence
machine starts from the absent state.
- Second commit wires slsa-builder #86's new tags-only `source-ref`
input (pin now `496e40e5ea983cd6933de0dec6b2658c6ccb4db6`):
`workflow_dispatch` passes `refs/tags/<release_tag>` so the build
resolves, peels, and checks out the signed tag commit regardless of the
dispatch ref, and the signed predicate binds to the built tag identity
while recording the dispatch context as signed invocation members. Tag
pushes pass the exact empty string (byte-compatible with the previous
contract). The dispatch guard relaxes from "must originate at the tag
ref" to a `vX.Y.Z` shape check; tag existence/version agreement fail
closed downstream (caller checkout, `source-ref-invalid`,
tag/version/CHANGELOG agreement).
- Third commit updates `docs/release.md`: the dispatch IMPORTANT block
now documents the source-ref mechanics (dispatch from any ref; build and
provenance bind to the signed tag commit; dispatch context recorded as
signed invocation members), the publish job input list covers
`source-ref` empty-on-push behavior, the provenance model notes the
invocation-member claims, and failure recovery gains the pipeline-defect
rerun path (fix on main, dispatch with `release_tag`, no tag
recreation).

## Related Issues

- Related windlasstech/slsa-builder#30 (P06 live M1 dogfood — this
unblocks the retry)
- Related windlasstech/slsa-builder#80 (the consumed fix)

## Change Type

- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [x] Test/CI
- [ ] Breaking change
- [ ] Other: <!-- describe -->

## Changelog

- Category: None
- User-facing note: CI-only builder pin bump; the 0.1.2 changelog
already records the pipeline change, and no package content changes.

Changelog update:

- [ ] `CHANGELOG.md` `[Unreleased]` updated
- [x] Not needed because this change is not user-facing

## Checklist

### General

- [x] PR title follows [Conventional
Commits](https://www.conventionalcommits.org/) format: `type(scope):
Summary`
- [x] This PR does not expose backend/internal implementation details in
a public repo.
- [x] No secrets, tokens, keys, or private endpoints are included.
- [x] Changes stay within this repository's intended scope.

### CI/Workflow Changes (if applicable)

- [x] All `uses:` references are pinned to full 40-character commit SHAs
(with `# vX.Y.Z` comment) — the slsa-builder reference carries the
documented `# main, pre-release` comment convention until slsa-builder
cuts releases.
- [x] `step-security/harden-runner` is included as the first step in
every job — unchanged; this PR touches only the pin line.
- [x] Job-level `permissions` are used instead of top-level
`permissions` — unchanged (top-level `permissions: {}` with job-level
elevation).

### Protocol / Compatibility Impact

- [x] No protocol/spec impact

## Testing

- [ ] Unit tests added/updated
- [ ] Integration or conformance tests added/updated
- [ ] Tests pass
- [x] Lint and format pass
- [ ] Type check passes
- [x] Manual verification performed

Describe test evidence: one-line pin change; `actionlint` exit 0 on
`publish.yml`; `oxfmt` clean. The consumed fix was verified against the
failure signature: the failed run's build job emitted
`{"category":"package-resolution-invalid","check":"npm_profile.selection",...}`,
which is exactly the case windlasstech/slsa-builder#80 handles
(settings-only `pnpm-workspace.yaml` as terminal standalone-root
package). End-to-end validation happens on the v0.1.2 retry run.

## Documentation

- [ ] README updated
- [ ] Spec/docs updated
- [x] Changelog decision completed above

## Rollout / Risk

- Risk level: Low
- Rollback plan: revert this commit to restore the previous pin; the
retry is gated on this merge either way.

## Reviewer Checklist

- [x] Scope is clear and minimal
- [x] Security and boundary checks passed
- [x] Tests and docs are sufficient
- [x] Compatibility impact is correctly handled

---------

Signed-off-by: Yunseo Kim <git@yunseo.kim>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant