Skip to content

feat: self-healing idempotent publish + tested decision logic + CI#2

Merged
dean-harel-unipaas merged 3 commits into
mainfrom
feat/idempotent-publish
Jun 10, 2026
Merged

feat: self-healing idempotent publish + tested decision logic + CI#2
dean-harel-unipaas merged 3 commits into
mainfrom
feat/idempotent-publish

Conversation

@dean-harel-unipaas

Copy link
Copy Markdown
Contributor

Why

While adding a CI idempotency check, I found the publish guard had a self-heal gap. Trace a partial failure: a run commits + pushes the spec (provenance sequence=100) but gh release create then fails. The retry (same pipeline, sequence=100) hit the ordering guard 100 <= 100 -> "stale", skip, and exited before the release-exists check. The release was never created, and consumers on releases/latest stayed on the prior release until some future content change cut a different one. The releaseExists guard was effectively unreachable because ordering/change-detection short-circuited first.

What

  • Extract the decision logic into scripts/publish-logic.ts (classify / specsEqual / canonicalJson), pure and unit-tested, with publish.ts as the I/O around it.
  • Fix the gap: ordering uses strict < (only a strictly-older deploy is stale), and an unchanged result no longer exits, it ensures the release recorded in provenance.tag exists, creating it if missing (self-heal). changed commits then ensures the new tag's release.
  • Result: idempotent (same sequence + content + existing release -> no-op), self-healing (a missing release for current content gets created), and still churn-free (no new release when content is unchanged).
  • CI (.github/workflows/ci.yml): typecheck + tests on push/PR, the repo had none. 11 tests cover stale / idempotent re-run / no-churn / changed / first-publish / rollback.

Behavior matrix

Situation Before After
New content, new deploy publish publish
Same deploy re-run, full success no-op no-op
Same deploy re-run, release had failed dropped as stale (release stays missing) self-heals (creates the release)
Newer deploy, identical content no-op no-op
Strictly older deploy skip skip

Note (out of scope)

npm run lint:spec currently crashes on the committed spec (Cannot read properties of null (reading 'enum')) - a Spectral issue, not a lint finding. I left lint out of CI so it stays green; worth chasing as part of the Phase 3 gate work since it relies on Spectral.

Extract ordering/change-detection into publish-logic.ts (classify/specsEqual/
canonicalJson), unit-tested. Fix the self-heal gap: ordering uses strict '<' and
an equal-sequence re-run is no longer skipped, so a run that committed but failed
to create the release heals on retry instead of being dropped as stale. Unchanged
content with an existing release stays a no-op (no churn).
@dean-harel-unipaas
dean-harel-unipaas merged commit bd04aa8 into main Jun 10, 2026
1 check passed
@dean-harel-unipaas
dean-harel-unipaas deleted the feat/idempotent-publish branch June 10, 2026 19:10
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.

1 participant