release: 0.2.9 with attestation propagation gate

Prior releases published ~3s after attesting, so Obsidian's dashboard could
scan the release before the attestation propagated to GitHub's public API and
cache a failed verdict — matching a same-day report from another plugin author
where every post-hoc check (gh attestation verify, one attestation per digest,
matching repo/commit) passed yet the dashboard still failed.

- Gate release creation on gh attestation verify succeeding against the public
  API (retry loop), so we never publish before the attestation is queryable.
- Upgrade to actions/attest-build-provenance@v4.
- Fresh 0.2.9 asset digests (styles.css comment + main.js version banner) so
  the new scan runs against digests with a single clean attestation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sirwan Afifi 2026-07-17 16:17:36 +01:00
parent 15765db2de
commit bf23ed7bd0
6 changed files with 43 additions and 16 deletions

View file

@ -1,16 +1,20 @@
name: Release
# Triggered on push to main. When manifest.json's version does not yet have a
# GitHub release, this builds, attests, tags, and publishes it.
# GitHub release, this builds, attests, waits for attestation propagation, then
# tags and publishes.
#
# The workflow deliberately runs in a BRANCH context (refs/heads/main) rather
# than being triggered by a tag push. Artifact attestations are signed with the
# workflow's OIDC identity, and that identity embeds the triggering ref. Obsidian's
# community-plugin verifier expects the signer identity to be a branch
# (refs/heads/*); a tag-triggered run signs as refs/tags/<version>, which fails
# Obsidian's verification ("attestation does not match this repository") even
# though `gh attestation verify` accepts it. Creating the tag from within this
# branch run keeps the attestation identity on refs/heads/main.
# Ordering is load-bearing for Obsidian's community-plugin dashboard scan:
# 1. Build assets. main.js embeds the version via the esbuild banner and
# styles.css carries a version comment, so every release has fresh asset
# digests. GitHub attestations are keyed by sha256 digest; a reused digest
# accumulates attestations from multiple runs and confuses verification.
# 2. Attest all three assets (branch-context OIDC identity, refs/heads/main).
# 3. WAIT until the attestation is verifiable through GitHub's public API.
# Obsidian scans the release the instant it is published; if we publish
# before the attestation propagates, the scan sees no valid attestation and
# the failed verdict is cached against the entry.
# 4. Only then create the tag and publish the release.
on:
push:
@ -62,12 +66,34 @@ jobs:
# the distributable assets. Attest all three published files.
- name: Attest release assets
if: steps.exists.outputs.found == 'false'
uses: actions/attest-build-provenance@v1
uses: actions/attest-build-provenance@v4
with:
subject-path: |
main.js
manifest.json
styles.css
# The attestation must be queryable through GitHub's PUBLIC attestation API
# before we publish the release. Obsidian's dashboard scans the release the
# moment it appears; if the attestation has not propagated yet, the scan
# reads "no valid attestation" and the failed verdict sticks. Gate the
# release on a real verify against the public API (retry until it lands).
- name: Wait for attestation propagation
if: steps.exists.outputs.found == 'false'
env:
GH_TOKEN: ${{ github.token }}
run: |
for attempt in $(seq 1 30); do
if gh attestation verify main.js -R "${{ github.repository }}" \
&& gh attestation verify styles.css -R "${{ github.repository }}" \
&& gh attestation verify manifest.json -R "${{ github.repository }}"; then
echo "Attestations verified via public API on attempt $attempt."
exit 0
fi
echo "Attempt $attempt: attestations not yet verifiable; retrying in 10s..."
sleep 10
done
echo "::error::Attestations did not become verifiable within the timeout."
exit 1
- name: Create tag
if: steps.exists.outputs.found == 'false'
env:

View file

@ -1,7 +1,7 @@
{
"id": "ink-layer",
"name": "Inkplane",
"version": "0.2.8",
"version": "0.2.9",
"minAppVersion": "1.7.2",
"description": "An infinite handwriting canvas with note embeds, designed for Apple Pencil and other pens.",
"author": "Sirwan Afifi",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "inkplane",
"version": "0.2.8",
"version": "0.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "inkplane",
"version": "0.2.8",
"version": "0.2.9",
"license": "MIT",
"dependencies": {
"perfect-freehand": "^1.2.3"

View file

@ -1,6 +1,6 @@
{
"name": "inkplane",
"version": "0.2.8",
"version": "0.2.9",
"description": "A Pencil-first infinite drawing canvas with embeds for Obsidian.",
"private": true,
"type": "module",

View file

@ -1,4 +1,4 @@
/* Inkplane 0.2.8 plugin styles. */
/* Inkplane 0.2.9 plugin styles. */
.view-content.ink-canvas-view-content {
height: 100%;

View file

@ -8,5 +8,6 @@
"0.2.5": "1.7.2",
"0.2.6": "1.7.2",
"0.2.7": "1.7.2",
"0.2.8": "1.7.2"
"0.2.8": "1.7.2",
"0.2.9": "1.7.2"
}