Skip to content

feat(apply,scan): generate OpenVEX document inline via --vex#94

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
feat/embedded-vex-apply-scan
May 30, 2026
Merged

feat(apply,scan): generate OpenVEX document inline via --vex#94
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
feat/embedded-vex-apply-scan

Conversation

@mikolalysenko
Copy link
Copy Markdown
Collaborator

@mikolalysenko Mikola Lysenko (mikolalysenko) commented May 29, 2026

Summary

Adds an optional --vex <path> argument to apply and scan. On a successful run the command writes an OpenVEX 0.2.0 document to that path using the same engine as the standalone vex command — so a single invocation can both apply/scan patches and emit the attestation, the natural shape for CI and bot workflows.

socket-patch apply --vex socket.vex.json
socket-patch scan --json --sync --yes --vex socket.vex.json

How it works

  • Core refactor: the product-resolve → verify → build → serialize → write → telemetry pipeline was extracted out of vex::run into reusable generate_vex / generate_vex_from_manifest_path helpers (plain VexBuildParams / VexWriteSummary / VexGenError types). The standalone vex command now calls this helper — no behavior change (its e2e suite still passes unmodified).
  • Embedded flags: --vex is the trigger; --vex-product / --vex-no-verify / --vex-doc-id / --vex-compact mirror the standalone knobs (namespaced to avoid colliding with apply's --force vocabulary; reuse the SOCKET_VEX_* env vars). Inert unless --vex is set.

Contract

  • Always written to the file, never stdout, so it never races the command's own --json output.
  • Fail-the-command: a requested-but-failed VEX (no detectable product, empty/missing manifest, nothing verified, unwritable path) flips the exit code even when the apply/scan itself succeeded, surfacing a stable error code in the JSON envelope (apply) / result (scan).
  • Built from the post-run manifest, verified against on-disk state (unless --vex-no-verify). Generated for real applies, --dry-run, and read-only scans alike.
  • JSON success adds a top-level vex summary { path, statements, format: "openvex-0.2.0" }.
  • apply's no-manifest early no-op does not trigger VEX generation — nothing to attest.

Release CI: automatic CHANGELOG rollover

Also wires a changelog-rollover job + scripts/rollover-changelog.sh helper into release.yml. After every artifact publishes, it promotes ## [Unreleased] to ## [<version>] — <date>, leaves a fresh empty [Unreleased], and commits it back to the release branch ([skip ci]).

  • Idempotent and runs after publish, so it never fails a release: no-op when a ## [<version>] heading was written by hand or when [Unreleased] is empty.
  • The pre-publish version-check now also accepts a non-empty [Unreleased] as valid release notes (in addition to an explicit heading), so maintainers can just add entries under [Unreleased] and let the rollover stamp them.

Tests & docs

  • New tests/e2e_embedded_vex.rs (5 tests): apply parity, JSON envelope vex field, apply fail-the-command, scan --vex-no-verify success, scan verify-failure error.
  • Parse-test coverage in cli_parse_{apply,scan}.rs; updated the in-process test fixtures for the new field.
  • CLI_CONTRACT.md, README.md, and CHANGELOG.md (Unreleased) updated.

Full CLI suite + new e2e tests pass with and without the cargo feature; clippy clean. Rollover helper verified locally (promote, idempotency, existing-heading no-op, empty-Unreleased no-op).

🤖 Generated with Claude Code

Add an optional `--vex <path>` argument to `apply` and `scan`. On a
successful run, the command writes an OpenVEX 0.2.0 document to that path
using the same engine as the standalone `vex` command, so a single
invocation can both apply/scan patches and emit the attestation — the
natural shape for CI and bot workflows.

Core refactor: extract the product-resolve -> verify -> build -> serialize
-> write -> telemetry pipeline out of `vex::run` into reusable
`generate_vex` / `generate_vex_from_manifest_path` helpers (plain
VexBuildParams / VexWriteSummary / VexGenError types). The standalone `vex`
command now calls this helper with no behavior change.

Embedded contract:
- `--vex` is the trigger; `--vex-product` / `--vex-no-verify` /
  `--vex-doc-id` / `--vex-compact` mirror the standalone knobs (namespaced
  to avoid colliding with apply's --force vocabulary; reuse SOCKET_VEX_*).
- Always written to the file, never stdout, so it never races --json.
- Fail-the-command: a requested-but-failed VEX flips the exit code even
  when the apply/scan itself succeeded, surfacing the error in the JSON
  envelope (apply) / result (scan) with a stable code.
- Built from the post-run manifest, verified against on-disk state;
  generated for real applies, --dry-run, and read-only scans alike.
- JSON success adds a top-level `vex` summary { path, statements, format }.

Tests: new e2e_embedded_vex.rs (apply parity, envelope field, fail path,
scan no-verify success, scan verify-failure error); parse-test coverage in
cli_parse_{apply,scan}; update CLI_CONTRACT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an Unreleased "Added" changelog entry, document the `--vex` / `--vex-*`
flags in the apply & scan README tables with examples, and add an
"Inline VEX on apply / scan" subsection covering the fail-the-command
contract and JSON summary surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `scripts/rollover-changelog.sh` and a post-publish `changelog-rollover`
job. After every artifact publishes, the job promotes `## [Unreleased]` to
`## [<version>] — <date>` and leaves a fresh empty `[Unreleased]` for the
next cycle, then commits it back to the release branch (`[skip ci]`).

The helper is idempotent and runs after publish, so it never fails the
release: it's a no-op when a `## [<version>]` heading was written by hand or
when `[Unreleased]` is empty, leaving the file byte-identical so there's
nothing to commit.

To make the new flow usable end-to-end, the pre-publish version-check now
accepts a non-empty `[Unreleased]` section as valid release notes (in
addition to an explicit `## [X.Y.Z]` heading), so maintainers can just add
entries under `[Unreleased]` and let the rollover stamp them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Empty commit to re-trigger the CI workflow — the previous push
(a7ad4cf) did not fire a pull_request run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Audit GHA Workflows check (zizmor) flagged a High template-injection:
`${{ github.ref_name }}` expanded directly inside the push `run:` block can
inject attacker-controllable code via a crafted branch name. Pass workflow
contexts (`github.ref_name`, `needs.version.outputs.version`) through `env:`
and reference them as shell variables instead.

Verified clean with `zizmor .github --gh-token <tok> --min-severity medium`
(the exact CI invocation): "No findings to report."

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) merged commit d7f57d9 into main May 30, 2026
32 checks passed
@mikolalysenko Mikola Lysenko (mikolalysenko) deleted the feat/embedded-vex-apply-scan branch May 30, 2026 12:40
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.

2 participants