Skip to content

DRAFT: reusable pypi-publish + release model (org release automation)#16

Draft
ywatanabe1989 wants to merge 1 commit into
mainfrom
feat/reusable-pypi-publish-and-release-model
Draft

DRAFT: reusable pypi-publish + release model (org release automation)#16
ywatanabe1989 wants to merge 1 commit into
mainfrom
feat/reusable-pypi-publish-and-release-model

Conversation

@ywatanabe1989

Copy link
Copy Markdown
Contributor

DRAFT proposal — org-level release automation (do NOT merge)

This is a draft for review, not a change to land this run. It adds the
missing org release automation that 68 repos currently inline (and drift) —
there is today no pypi-publish reusable in scitex-ai/.github at all, so
those copies cannot be delegated. This PR creates that reusable and implements
the operator-confirmed release model (TG 2026-07-23).

What's here

  • Reusable B.github/workflows/pypi-publish-and-github-release-on-tag.yml
    (on: workflow_call): build -> containment guard -> publish -> GitHub
    Release, parametrized hosted | sif via the publish-backend input.
  • Reusable A.github/workflows/promote-develop-to-main-on-tag.yml
    (on: workflow_call): CI on the tagged commit (org pytest-matrix), then
    auto-open + auto-merge develop->main with a MERGE commit (never squash).
  • Thin callerworkflow-templates/release.yml (+ .properties.json):
    the 2-job file a leaf repo copies.
  • RELEASE-MODEL.md — flow diagram, guard, feedback loop, adoption steps,
    a worked scitex-dev (SIF) example, and the human-decision list.

The two publish backends (one input selects the path)

backend runner (runs-on-json) build publish
hosted '"ubuntu-latest"' setup-python + python -m build pypa/gh-action-pypi-publish (Docker, OIDC trusted publisher)
sif '["self-hosted","Linux","X64","scitex-ci"]' .github/ci/build-in-sif.sh .github/ci/publish-in-sif.sh (manual OIDC inside the SIF — no Docker/Python on the bare Spartan node)

A sif repo must never receive a hosted runs-on-json — respects the
no-hosted-runners-guard posture. Learned from the two real inlined variants
(scitex-io = hosted, scitex-dev/scitex-plt = sif).

The containment guard (loud-fail, works under squash OR ff)

TAG_SHA="$(git rev-list -n1 "refs/tags/${TAG}")"
git fetch --no-tags --prune origin main
git merge-base --is-ancestor "${TAG_SHA}" origin/main \
  || { echo "::error::CONTAINMENT GUARD FAILED — tag not in main"; exit 1; }

A squash merge silently mints a new SHA and drops the tag's commit from main's
history; the guard turns that into a red abort at publish time instead of a
mis-versioned release. Reusable A also merges --merge (never --squash) and
re-checks containment right after merging (belt + braces).

Actor feedback (A->B => B->A)

Each job writes a repo + stage + tag + verdict line to
$GITHUB_STEP_SUMMARY; B's final report job emits a consolidated verdict and
fails red on any stage failure, so github.actor's run-failure
notification names the repo + stage. # FLEET-HOOK: markers show exactly where
a scitex-todo card / DM to the actor plugs in later.

Decisions-for-review (defensible defaults, implemented)

  • Secrets: both backends use OIDC trusted publishing, so no API-token
    secret is required
    ; B declares one optional PYPI_API_TOKEN as the
    documented fallback for token-based repos.
  • A is a reusable (not just documented triggers) so the caller can chain B
    after it with a single needs: promote edge — both must be sibling jobs in
    one caller workflow for needs to gate B on A.
  • runs-on-json via fromJSON unifies the array (self-hosted) and bare
    string ("ubuntu-latest") cases in one field.

Human decisions still needed

  • PyPI trusted-publisher config re-verified per package (~68) before first
    real release — the single biggest gate.
  • Hosted-only CI: A's CI delegates to the self-hosted pytest-matrix
    reusable (uses: can't be an expression); hosted-only repos need a hosted CI
    variant or a runs-on input on pytest-matrix.yml.
  • --admin merge with a merge commit must be permitted by each repo's
    branch protection (squash-only would trip the guard).
  • The 5 CALLER+extra repos whose current inlined release has extra steps
    (docs/DOI/extra gates) — each extra must become a B input or a caller
    companion job; enumerate + decide per repo.

Validation

  • yaml.safe_load parses all 4 YAML/JSON files.
  • actionlint 1.7.1clean (exit 0, no findings) on both reusables.

DRAFT. Not merged, not marked ready, not deployed to any leaf repo.

Add the missing org-level release automation as two reusable workflows
plus the release-model doc. DRAFT — not adopted by any leaf repo.

- reusable B: pypi-publish-and-github-release-on-tag.yml (workflow_call)
  build + CONTAINMENT-GUARD + publish + GitHub Release, parametrized
  hosted|sif via `publish-backend`. The guard asserts the tag commit is
  an ancestor of origin/main (git merge-base --is-ancestor) and aborts
  RED otherwise — makes the silent squash-merge failure loud.
- reusable A: promote-develop-to-main-on-tag.yml (workflow_call)
  CI on the tagged commit (org pytest-matrix), then auto-open + auto-merge
  develop->main with a MERGE commit (never squash). Chained from B via the
  caller's `needs: promote`.
- workflow-templates/release.yml (+ .properties.json): the thin 2-job
  caller a leaf repo copies.
- RELEASE-MODEL.md: flow, guard, actor-feedback loop, adoption steps, a
  worked scitex-dev (SIF) example, and the human-decision list.

Validated: yaml.safe_load parses all four; actionlint 1.7.1 clean on both
reusables.
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