From 169ece0cdcc7f3f2a5381f752a5e8c124949e8c4 Mon Sep 17 00:00:00 2001 From: alexxxnikolskiy Date: Sun, 26 Jul 2026 22:11:20 +0300 Subject: [PATCH] =?UTF-8?q?fix(release):=200.1.0=20=D1=83=D0=B5=D1=85?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=B0=D0=BD=D1=81=D0=B0=20=E2=80=94=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D1=82=D1=8C=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D1=8F=D1=82=D1=8C=20=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82,=20=D0=B0=20=D0=BD=D0=B5=20=D0=BD=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Опубликованная 0.1.0 не несёт аттестации, хотя шаг называется «with provenance», а preflight требует provenance: true. Реестр это подтверждает: у @trdlabs/engine@0.1.0 attestations пусты. Причин две, и обе молчаливые. 1. В манифесте не было поля repository. npm документирует публичный repository, совпадающий с источником публикации, как ПРЕДУСЛОВИЕ провенанса. Без него publish всё равно успешен — просто без аттестации. 2. pnpm publish сначала пробует собственный OIDC-обмен; тот вернул 404 (trusted publisher для пакета не настроен), pnpm откатился на токен и пропустил генерацию провенанса, напечатав при этом «✅ Published». Починено: добавлен repository; publish переведён на `npm publish --provenance` (npm CLI берёт Sigstore-идентичность прямо из id-token: write); preflight теперь требует repository. Но главный урок не в этих двух строках: шаг УТВЕРЖДАЛ провенанс, и ничто это не проверяло. Поэтому после публикации workflow спрашивает сам реестр — единственную инстанцию, которая знает, что реально прикрепилось, — и падает, если аттестации нет, прямо говоря, что версия в npm неизменяема и чинится только следующим патчем. Версия поднята до 0.1.1: 0.1.0 исправить в реестре нельзя. --- .github/workflows/release.yml | 24 +++++++++++++++++++++++- package.json | 6 +++++- scripts/assert-version-publishable.mjs | 6 ++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5daaa2f..4650471 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,10 +112,32 @@ jobs: run: pnpm verify:package - name: Publish to npm (with provenance) - run: pnpm publish --access public --no-git-checks + # `npm publish`, not `pnpm publish`: pnpm attempts its own OIDC token exchange first, and + # when that 404s (no trusted publisher configured) it falls back to the token and silently + # SKIPS provenance — which is exactly how 0.1.0 shipped unattested while this step printed + # success. The npm CLI generates the Sigstore attestation directly from the Actions OIDC + # identity granted by `id-token: write`. + run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Verify the published version actually carries provenance + # A step named «with provenance» that nobody checks is a claim, not a fact — the previous + # release proved it. The registry is the only authority on what was attached, so ask it. + # Registry reads for a fresh publish can lag, hence the bounded retry rather than one shot. + run: | + for attempt in $(seq 1 20); do + body=$(curl -sf "https://registry.npmjs.org/-/npm/v1/attestations/@trdlabs/engine@${VERSION}" || true) + if echo "$body" | grep -q 'slsa.dev/provenance'; then + echo "provenance attestation present for ${VERSION}" + exit 0 + fi + sleep 15 + done + echo "PUBLISHED WITHOUT PROVENANCE: no attestation for @trdlabs/engine@${VERSION}" + echo "npm is immutable, so this version cannot be fixed in place — cut the next patch." + exit 1 + - name: Tag the release (secondary release note) run: | git tag "v${VERSION}" diff --git a/package.json b/package.json index 5c147b1..39f85a0 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "@trdlabs/engine", - "version": "0.1.0", + "version": "0.1.1", "description": "One deterministic execution core (decision → risk → pending order → fill → portfolio → canonical trace) shared by backtester and platform.", "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/trdlabs/engine.git" + }, "type": "module", "engines": { "node": ">=22" diff --git a/scripts/assert-version-publishable.mjs b/scripts/assert-version-publishable.mjs index 4a00be9..be4a189 100644 --- a/scripts/assert-version-publishable.mjs +++ b/scripts/assert-version-publishable.mjs @@ -27,6 +27,12 @@ if (pkg.version === '0.0.0') { if (pkg.publishConfig?.provenance !== true) { problems.push('publishConfig.provenance must be true (OIDC provenance, sdk parity)'); } +// npm documents a matching public `repository` as a prerequisite for provenance. Without it the +// publish still succeeds — it just silently ships no attestation, which is how `0.1.0` went out +// claiming provenance it did not have. +if (typeof pkg.repository?.url !== 'string' || !pkg.repository.url.includes('github.com/trdlabs/engine')) { + problems.push('repository.url must point at github.com/trdlabs/engine (npm provenance prerequisite)'); +} if (pkg.license !== 'Apache-2.0') { problems.push(`license must be Apache-2.0 (got "${pkg.license}")`); }