chore(release): adopt shared forensic release pipeline#289
Conversation
Replace PodNotes' four in-repo release workflows with three thin caller stubs that consume the reusable pipeline in chhoumann/obsidian-plugin-workflows (@v2). The release-plan and release-contract scripts (and their tests) moved into that shared repo; this repo now carries only trigger + uses: stubs. - release-prepare.yml: workflow_run(Test) + workflow_dispatch escape hatch - release-trigger.yml: pull_request_target(closed), no workflows-ref - release.yml: workflow_dispatch, npm assets + docs build verify commands - delete scripts/{release-plan,release-contract}.mjs + tests, semantic-release-plugins.d.ts, and release-workflow-contract.test.ts (its subject, the inline workflow JS, is removed) - drop the release:plan script and the now-unused semantic-release devDependencies (they live in the shared repo) Releases are blocked until the podnotes-release-bot GitHub App is created and RELEASE_APP_ID / RELEASE_APP_PRIVATE_KEY are set; CI is unaffected.
Deploying podnotes with
|
| Latest commit: |
a12a2d0
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f1ed0ea5.podnotes.pages.dev |
| Branch Preview URL: | https://chore-shared-release-pipelin.podnotes.pages.dev |
|
Ready for your merge. All checks are green (Test, CodeQL x2, Analyze actions, Cloudflare Pages) and no review threads are outstanding. I am not merging - this is yours to merge per the PR-to-release standard. One dependency before the next release: this PR deletes the old release flow, so releases are blocked until the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb2c16b3eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A commit on the default branch may raise manifest.minAppVersion before the release that materializes it into versions.json. The contract previously required strict equality between the current version's recorded floor and the manifest floor, so any floor bump landed on the default branch broke every subsequent Prepare and validate run until hand-edited. Port the pending-floor semantics from PodNotes' local pipeline (its 2.18.4 fix): the manifest floor must equal the recorded floor or increase from it, never fall below it. Tag-state checks stay strict because a tag always materializes its floor. Found by Codex review on chhoumann/PodNotes#289.
Pins the three reusable-workflow refs and both workflows-ref inputs to the exact obsidian-plugin-workflows commit (v3: pending-floor support, no chore releases, self-CI) instead of the floating v2 tag. Every pipeline stage resolves the same toolkit commit, closing the mid-flight tag-move race found in adversarial review, and matching the repo's SHA-pinning posture for all other actions.
What and why
Migrates PodNotes onto the shared forensic release pipeline in
chhoumann/obsidian-plugin-workflowsas its first, parity-proof consumer. PodNotes' own machine-PR release flow was
the model for that shared repo, so this is a "fold the reference implementation
back into the library" migration: a fix now lands once in the shared repo and
every plugin picks it up by bumping a pin.
Christian's decision this encodes:
release PR is the sole release act. No auto-release, no manual dispatch on the
happy path.
tamper-evident is preserved in the shared reusables (see the parity table).
GitHub App bot the maintainer provisions and controls, replacing the ambient
github-actions[bot].The four in-repo release workflows and the release toolkit scripts are replaced
by three thin caller stubs (trigger +
uses:); all logic lives in the sharedreusables pinned at
@v2withworkflows-ref: v2.Line delta
semantic-releasedependency treeParity audit
Every check in the old PodNotes workflows was read line by line and mapped to
its new home in the shared reusables. Fully preserved:
release/<semver>branch, exact title)release-prepare.ymlopen-pr; re-checked inrelease-validate.yml+release.ymlresolverelease.ymlbuild "Validate release commit and version-file diff";release-contract.mjs validateVersionFilesrelease-validate.ymlhead-commit check;release.ymlresolverelease-validate.yml;release.ymlresolverelease-validate.ymlvalidaterelease-run/<version>release-validate.yml, consumed/cleaned inrelease.ymlpublishrelease.ymlattest + publish (attest-build-provenance,gh attestation verify, per-asset re-download + sha256)release-prepare.yml"Classify current commit" + "Verify previous release is public"release-validate.yml+release.ymlresolve (exactrelease(version): Release <v> (#<PR>))release-validate.ymlcompatibility block;release-contract.mjs validateVersionFiles/materializeVersionFilesDocumented deviations (intended)
github-actions[bot]; the shared flow authors them aspodnotes-release-bot[bot]via a GitHub App token minted in prepare. The GitHub release object is still
created by
GITHUB_TOKEN(author staysgithub-actions[bot]).test.ymlon the release branch. The shared flow relies on the App-authoredPR triggering
pull_requestCI naturally (PodNotes'Testworkflow runs onpull_request), so the explicit dispatch is unnecessary.Additional deviations found and flagged (not silently dropped)
These are real differences between the old PodNotes internals and the shared
reusables. None block CI; they are behavioral notes for a follow-up on the
shared repo (the stubs carry no logic, so they cannot be fixed here):
retryTransientGitHubReadis absent. The oldrelease-trigger.ymlwrapped every read-only GitHub API call in a bounded 5xx retry
(
[500,1500,4000]ms). The shared reusables have no equivalent genericread-retry wrapper. Impact: a transient GitHub 5xx during validation fails the
run instead of retrying - resilience only, not a security guarantee.
The old compatibility contract asserted
manifest.minAppVersionis a stablesemver and monotonically increases, and compared
versions.jsonhistoryagainst the previous stable tag exactly. The shared reusables preserve the
new-entry floor mapping and the base-vs-release history check, but only
type-check minAppVersion as a non-empty string and compare history against the
tested base (not the previous tag). Impact: defense-in-depth on the
compatibility floor is reduced; the core "you can't rewrite released history
or mismap the new floor" guarantee remains.
(#PR)suffix is now mandatory (was optional in the oldvalidator). GitHub squash-merge appends
(#<PR>)by default, so this matchesPodNotes' actual merge settings - stricter, not a loss. Confirm squash-merge
stays enabled.
Test-file note
scripts/release-workflow-contract.test.tswas deleted (it was not in theoriginal migration list). It asserted the inline JavaScript of the old
release-trigger.yml/release.yml(merge-subject validator, tag-visibilitycontract, compatibility transition, transient-retry). That inline code is
exactly what the thin stubs remove, so the test's subject no longer exists here.
Those behaviors are now covered by the shared repo's own
scripts/*.test.mjs(for the toolkit functions) and the parity table above (for the inline
workflow forensics).
Dry-run plan evidence
Ran the shared repo's
release-plan.mjsagainst this clone's real git history(
node scripts/release-plan.mjs --cwd <podnotes> --package-manager npm):Current HEAD (
6ac0187, sole post-tag commit isrefactor: remove unused capability kernel) - correctly declines a release:{ "baseSha": "6ac0187...", "release": false, "notes": "", "schemaVersion": 1 }Historical base
dd50552(the commit the real 2.19.2 released from) -reproduces the actual release exactly:
{ "baseSha": "dd50552...", "previousVersion": "2.19.1", "nextVersion": "2.19.2", "releaseType": "patch", "release": true, "notes": "## [2.19.2](.../compare/2.19.1...2.19.2) ...\n### Bug Fixes\n* **orca:** invoke the runner via the worktree-local bin ... (#285) ..." }Gates (all green locally)
npm run lint,npm run format:check(oxfmt reformatted the copied stubs toPodNotes' 2-space style),
npm run typecheck,npm run test(1048 tests / 72 files, deleted script tests dropped out cleanly),
npm run build. The three stubs passactionlintand YAML parse.REQUIRED BEFORE MERGE - GitHub App setup
The pipeline fails closed without a per-repo release-bot App. Before the next
release, Christian must (steps in the shared repo README, "Per-repo GitHub App
setup"):
PodNotes Release Botwith slugpodnotes-release-bot(must matchrelease-bot-app-slug). Repositorypermissions: Contents R/W, Pull requests R/W. No account perms, no webhook.
RELEASE_APP_ID(numeric App id) and secretRELEASE_APP_PRIVATE_KEY(the full.pem).masteris protected, add the App as an allowed bypass/merge actor per theruleset (the maintainer still merges; the App only authors).
CI is unaffected meanwhile (Test, docs, CodeQL etc. keep running). This PR
deletes the old release flow, so releases are blocked until the App is set up -
nothing else breaks. After setup, smoke-test via Actions -> Prepare release ->
Run workflow with the
dry-run: trueline uncommented before the first realrelease.
Do not auto-merge
Christian merges PodNotes himself after the App is provisioned.
Update while landing: shared workflows are now pinned to the exact
v3SHA (68068d2) rather than floating@v2. v3 = v2-parity + pending-minAppVersion floors (upstream #3, fixing the confirmed Codex finding here) + no more releases forchorecommits (upstream #4). Both upstream PRs were adversarially reviewed and are covered by the new upstream self-CI.