You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(apply,scan): generate OpenVEX document inline via --vex (#94)
* feat(apply,scan): generate OpenVEX document inline via --vex
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>
* docs: changelog + README for inline --vex on apply/scan
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>
* ci(release): auto-roll CHANGELOG [Unreleased] over after publish
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>
* chore: nudge CI
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>
* ci(release): fix zizmor template-injection in changelog-rollover
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>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# 2. The `## [Unreleased]` section is non-empty — the
50
+
# post-publish `changelog-rollover` job stamps it as
51
+
# `## [X.Y.Z] — DATE` after publishing.
52
+
if grep -qE "^## \[?${VERSION}\]?( |$)" CHANGELOG.md; then
53
+
echo "Found explicit CHANGELOG heading for ${VERSION}."
54
+
exit 0
52
55
fi
56
+
unreleased_content=$(awk '
57
+
/^## \[Unreleased\]/ { inblock=1; next }
58
+
inblock && /^## / { inblock=0 }
59
+
inblock && NF { print }
60
+
' CHANGELOG.md)
61
+
if [ -n "$unreleased_content" ]; then
62
+
echo "No explicit ${VERSION} heading, but [Unreleased] has content — it will be rolled over after publish."
63
+
exit 0
64
+
fi
65
+
echo "::error::CHANGELOG.md has no release notes for ${VERSION}."
66
+
echo "::error::Add entries under \`## [Unreleased]\` (preferred — they roll over automatically), or a \`## [${VERSION}] — $(date +%Y-%m-%d)\` heading, before re-running."
Copy file name to clipboardExpand all lines: README.md
+30Lines changed: 30 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -176,6 +176,8 @@ socket-patch scan [options]
176
176
|`--sync`| Sugar for `--apply --prune`. The canonical bot-mode flag. |
177
177
|`--batch-size <n>`| Packages per API request (default: `100`) |
178
178
|`--all-releases`| Store patches for every release/distribution variant, not just the installed one — makes the manifest portable across environments (e.g. cross-platform CI caches) |
179
+
|`--vex <path>`| On a successful scan, also write an OpenVEX 0.2.0 document to this path. See [Inline VEX generation](#inline-vex-on-apply--scan). (env: `SOCKET_VEX`) |
180
+
|`--vex-product`, `--vex-no-verify`, `--vex-doc-id`, `--vex-compact`| Passthrough to the embedded VEX builder; mirror the standalone [`vex`](#vex) knobs. Inert unless `--vex` is set. |
179
181
180
182
> Use `--dry-run` to preview what `--apply`/`--prune`/`--sync` would do without mutating disk.
|`-f, --force`| Skip pre-application hash verification (apply even if package version differs) |
227
+
|`--vex <path>`| On a successful apply, also write an OpenVEX 0.2.0 document to this path. See [Inline VEX generation](#inline-vex-on-apply--scan). (env: `SOCKET_VEX`) |
228
+
|`--vex-product`, `--vex-no-verify`, `--vex-doc-id`, `--vex-compact`| Passthrough to the embedded VEX builder; mirror the standalone [`vex`](#vex) knobs. Inert unless `--vex` is set. |
222
229
223
230
**Examples:**
224
231
```bash
@@ -236,6 +243,9 @@ socket-patch apply --offline
236
243
237
244
# JSON output for CI/CD
238
245
socket-patch apply --json
246
+
247
+
# Apply and emit an OpenVEX attestation in one step
Run `socket-patch get` or `socket-patch scan --sync` first — `vex` errors with `no_patches` against an empty manifest.
471
481
482
+
### Inline VEX on `apply` / `scan`
483
+
484
+
You don't need a separate `vex` invocation: pass `--vex <path>` to `apply` or `scan` and the same OpenVEX document is generated as a side-effect of a successful run.
485
+
486
+
```bash
487
+
# Patch and attest in one step
488
+
socket-patch apply --vex socket.vex.json
489
+
490
+
# Discover, apply, prune, and attest — the full bot-mode pass
The `--vex-product`, `--vex-no-verify`, `--vex-doc-id`, and `--vex-compact` flags mirror the standalone command's `--product` / `--no-verify` / `--doc-id` / `--compact` knobs.
495
+
496
+
Contract:
497
+
498
+
- The document is **always written to the file** (never stdout), so it never collides with the command's own `--json` output. JSON mode adds a top-level `vex` summary — `{ path, statements, format }` — to the envelope (`apply`) / result (`scan`).
499
+
- It's built from the manifest **as it stands after the run** (including any `--apply`/`--sync` writes) and verified against on-disk state unless `--vex-no-verify` is set. Generated for real applies, `--dry-run`, and read-only scans alike.
500
+
-**Fail-the-command:** if `--vex` was requested but generation fails (no detectable product, empty/missing manifest, nothing verified, unwritable path), the command exits non-zero **even when the apply/scan itself succeeded**, with a stable error code in the JSON output.
501
+
472
502
## Scripting & CI/CD
473
503
474
504
All commands support `--json` for machine-readable output. JSON responses always include a `"status"` field for easy error detection:
|`apply`, `scan`|`--vex`|`SOCKET_VEX`| Generate an OpenVEX 0.2.0 document at this path on a successful run; see "embedded VEX" below |
58
+
|`apply`, `scan`|`--vex-product`, `--vex-no-verify`, `--vex-doc-id`, `--vex-compact`|`SOCKET_VEX_PRODUCT`, `SOCKET_VEX_NO_VERIFY`, `SOCKET_VEX_DOC_ID`, `SOCKET_VEX_COMPACT`| Passthrough to the embedded VEX builder; mirror the standalone `vex` knobs. Inert unless `--vex` is set |
@@ -73,6 +75,18 @@ Beyond the globals above, each subcommand defines a small set of local arguments
73
75
74
76
The hidden alias `--no-apply` on `get --save-only` is **part of the contract** — it does not appear in `--help` but is widely used in existing scripts.
75
77
78
+
### Embedded VEX (`apply --vex` / `scan --vex`)
79
+
80
+
`--vex <path>` folds OpenVEX 0.2.0 generation into `apply` and `scan`: on a successful run the command writes the document to `<path>` using the same engine as the standalone `vex` command. The `--vex-*` flags mirror `vex`'s `--product` / `--no-verify` / `--doc-id` / `--compact` knobs (namespaced to avoid colliding with the host command), and reuse the standalone env vars (`SOCKET_VEX_PRODUCT`, etc.). They are inert unless `--vex` is set.
81
+
82
+
Contract details:
83
+
84
+
***Always written to the file** — never stdout — so the document never races the command's own `--json` output.
85
+
***Fail-the-command**: if `--vex` was requested but generation fails (product PURL undetectable, empty/missing manifest, all patches unverified, unwritable path), the command exits non-zero **even when the apply/scan itself succeeded**. In `--json` mode the failure surfaces in the envelope's `error` (`apply`) / top-level `error` (`scan`), with a stable code (`product_undetected`, `no_applicable_patches`, `write_failed`, …).
86
+
***Built from the post-run manifest**, verified against on-disk state (unless `--vex-no-verify`). Generated for real applies, `--dry-run`, and read-only `scan` alike.
87
+
***JSON success surface**: `apply` adds a top-level `vex` object to its envelope; `scan` adds a top-level `vex` key to its result. Both carry `{ path, statements, format: "openvex-0.2.0" }`.
88
+
*`apply`'s no-manifest early exit (the "No .socket folder found" success no-op) does **not** trigger VEX generation — there is nothing to attest.
89
+
76
90
`repair` keeps its `gc` visible alias.
77
91
78
92
## Environment variables
@@ -105,6 +119,7 @@ All v3.0 env vars use the `SOCKET_*` prefix. Three legacy `SOCKET_PATCH_*` names
105
119
|`SOCKET_ONE_OFF`|`get --one-off` / `rollback --one-off`|`false`| Local to `get`/`rollback`. |
106
120
|`SOCKET_SKIP_ROLLBACK`|`remove --skip-rollback`|`false`| Local to `remove`. |
107
121
|`SOCKET_DOWNLOAD_ONLY`|`repair --download-only`|`false`| Local to `repair`. |
122
+
|`SOCKET_VEX`|`apply --vex` / `scan --vex`| (none) | Embedded OpenVEX output path. The `SOCKET_VEX_*` knobs (`_PRODUCT`, `_NO_VERIFY`, `_DOC_ID`, `_COMPACT`) are shared with the standalone `vex` command; on `apply`/`scan` they bind to `--vex-product` etc. |
0 commit comments