Skip to content

design: rework multi-module release flow for independent versions #1002

Description

@dougborg

Design: rework multi-module release flow for independent versions

Status: proposal · Date: 2026-07-25 · Scope: katana-openapi-client, stocktrim-openapi-client, statuspro-openapi-client, frontapp-openapi-client

Recommendation in one line: move all four repos to release-please in manifest mode — one aggregated Release PR per repo, all tags created atomically on merge, and every build/publish job triggered by tags (or by the GitHub Release), so no release job ever pushes to main.


1. Verification of the stated failures

Every claim below was checked against real run logs, workflow files at HEAD, and repo settings. Three of five claims are confirmed as stated; two need correction.

✅ 1.1 Job race — katana run 30171587653 — CONFIRMED (but already fixed)

At the time of the run, release-client and release-mcp in .github/workflows/release.yml both keyed off needs: test and ran concurrently. release-mcp won; release-client failed at the "Python Semantic Release (Client)" step:

[LOCAL SHA] d23b26f4... != 84ad40dc... [UPSTREAM SHA]. Upstream branch 'origin/main' has changed!

MCP released (v0.115.0 + Docker), client did not.

Correction to the premise: PR #1001 is merged (2026-07-25T19:44:21Z) and is not a band-aid in the pejorative sense — it is a genuine dependency-graph serialization: release-mcp's needs changed from test to [test, release-client], with if: always() && needs.test.result == 'success' so MCP can still release when the client doesn't, plus ref: main and git fetch/reset --hard origin/main so release-mcp starts from the post-client-release tip. This specific race is closed. It does not, however, address the cascade or the direct-push-to-main topology.

✅ 1.2 Stale-checkout race — stocktrim run 30169605854 — CONFIRMED

Head SHA 3c2d1166. release-client succeeded (client v0.13.0). release-mcp (job 89708535182) failed at "Update MCP client dependency":

[main d017a1e] chore(mcp): update client dependency to v0.13.0
To https://github.com/dougborg/stocktrim-openapi-client
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '...'
hint: Updates were rejected because the tip of your current branch is behind

The checkout had no ref:, so it used the stale triggering SHA. Fixed point-wise by PR #236 (merged f7709511): adds ref: main, a "fast-forward to the post-release tip" step (git fetch origin main; git reset --hard origin/main), and git pull --rebase origin main before pushing.

✅ 1.3 Cascading self-trigger — stocktrim — CONFIRMED

release.yml is literally on: push: branches: [main] with no paths-ignore and no actor filter, so every bot commit re-enters the workflow. One human push produced three Release runs in ~4 minutes:

Run Head SHA Triggering commit Time (UTC) Result
30169605854 3c2d1166 ci(release): mint GitHub App tokens… 18:25:47 failure
30169656024 7938829e chore(release): client v0.13.0 18:27:18 failure
30169740059 cb2cc64d chore(release): mcp v0.16.0 18:29:50 success (no-op)

Each bot commit also fired CI, Security, and Deploy Documentation. The chain is exactly as described.

✅ 1.4 Immutable releases — stocktrim run 30169656024 — CONFIRMED, and permanent

"Attach .mcpb to GitHub release" failed:

HTTP 422: Cannot upload assets to an immutable release.
(https://uploads.github.com/repos/dougborg/stocktrim-openapi-client/releases/359829310/assets?label=&name=stocktrim-mcp-server-0.16.0.mcpb)

gh release view mcp-v0.16.0 shows immutable: true, assets: []. This is not recoverable — immutable releases can never accept assets afterwards. The bundle survives only as workflow artifact mcpb on run 30169656024, expiring 2026-10-23. client-v0.13.0 likewise has zero assets. PyPI publish for mcp 0.16.0 did succeed.

PR #237 (merged 03b93fad) fixes this forward by setting vcs_release: false on the MCP PSR invocation and replacing gh release upload --clobber with an awk changelog-extract step plus gh release create <tag> <file> --title --notes-file — i.e. create the release with its asset. GitHub's own guidance for immutable releases is the same shape: build assets first and create atomically, or create as draft → attach → publish (GitHub Docs: Immutable releases). This principle must be carried forward into any new design — see §5.4.

⚠️ 1.5 Inter-package dependency coupling — PARTIALLY MIS-STATED

The premise says "the MCP package pins the client version, so a client release requires a follow-up commit bumping the MCP's pin, plus a uv.lock re-sync commit." What's actually true differs per repo:

Repo MCP → client dependency (committed state) uv.lock re-sync commit?
katana katana-openapi-client>=0.80.0 (floating floor, not an exact pin) + [tool.uv.sources] … {workspace = true} Yes — a dedicated sync-lockfile job pushes chore: sync uv.lock after release to main after each release
stocktrim bare "stocktrim-openapi-client", no version constraint at all + workspace source No — release jobs never touch uv.lock
statuspro statuspro-openapi-client>=0.1.0 (was >=0.51.0) No landed commits — pipeline has never succeeded
frontapp frontapp-openapi-client>=0.51.0 No — push triggers disabled

So: the pin-bump commit is real in katana and attempted-in-CI in stocktrim, but stocktrim's pin never landed — the "Update MCP client dependency" step failed in run 1 and was skipped in run 2 (release-client.outputs.released was empty on the re-triggered run). Consequence worth noting on its own: stocktrim mcp 0.16.0 shipped to PyPI with an unconstrained client dependency. No chore(mcp): update client dependency commit exists in stocktrim's history at all.

The lockfile re-sync commit is real only in katana. It is the residue of katana issue #901 ("Release automation leaves root uv.lock behind the package pyproject version"), which is closed but whose proposed durable fix — fold uv lock into PSR's build_command and add uv.lock to assetswas never applied. Recent katana history still shows the two-commit pattern repeating: chore(release): client v0.81.0chore: sync uv.lock after releasechore(release): mcp v0.115.0chore: sync uv.lock after release.

🔎 1.6 Additional findings not in the premise

  • statuspro's release pipeline has never worked. Every push-triggered release.yml run fails at the semantic-release git-push step: fatal: could not read Username for 'https://github.com': terminal prompts disabled / exit 128 (e.g. run 26194603402, 2026-05-20) — a missing/invalid SEMANTIC_RELEASE_TOKEN. Same for release-ts.yml (run 26194603409). release-mcp.yml (tag-triggered) has zero runs, because the tag is never created.

  • frontapp's push triggers are commented out entirely, with the comment "Push trigger disabled pre-v0.1.0: PYPI_TOKEN / SEMANTIC_RELEASE_TOKEN aren't configured as repo secrets yet." Tracked in frontapp issue Fix AutoAPI duplicate object warnings and ensure generated code formatting consistency #10. Every historical run (pre-disable, 2026-04-24) failed the same way.

  • The pin-drift failure mode has already caused a real user-facing bug: statuspro issue MCP-04: Package and Deploy MCP Server to PyPI #63statuspro-mcp-server required client>=0.51.0 when only 0.1.0 existed on PyPI, i.e. an uninstallable package. Root cause per the issue: the constraint was copy-pasted from a sister repo (named as likely katana-openapi-client) at initial commit, and the automation meant to keep it truthful never ran. Fixed by hand in PR feat: implement Inventory domain class with full CRUD support (Phase 1 of #60) #62. frontapp carries the identical >=0.51.0 pin today with nothing published at all — the same bug, unfiled.

  • Two divergent MCP release surfaces. katana has both release.yml (push: main, releases MCP) and release-mcp.yml (push: tags: mcp-v*, publishes MCP to PyPI). statuspro/frontapp have the same duplication. These overlap and can double-publish.

  • statuspro/frontapp add two more self-trigger hops beyond the ones described: release.yml creates an mcp-v* tag → release-mcp.yml fires; and update-mcp-dependency.yml runs on: workflow_run after Release completes, commits the pin + lockfile, and git push origin main → re-enters release.yml.

  • statuspro and frontapp release*.yml are byte-for-byte structurally identical. Any redesign should be a single shared architecture applied to both.

  • Rulesets differ materially:

    Repo main protected? Required PR? Bypass actors
    katana yes ("Protect Main", id 6756621) yes (0 approvals) + required checks Admin role (id 5, always) and Integration app id 4392719 (= vars.RELEASE_PLEASE_APP_ID)
    statuspro yes (id 19738529, created 2026-07-25) yes + required checks Admin role + Integration app 4392719
    stocktrim yes (id 9325772) no — only deletion / non-fast-forward / linear-history / copilot review bypass_actors: []
    frontapp no ruleset at all

    Note stocktrim needs no bypass because it has no PR requirement — but it does enforce linear history, which is why fix(client): add missing is_archived field to UpdateProductRequest #236 had to use pull --rebase.

  • katana issue Supply-chain hardening for CI/CD (umbrella) #952 is a supply-chain-hardening umbrella (SHA-pinning, zizmor, Harden-Runner, Scorecard, SLSA) — orthogonal to release topology, no overlap. katana Migrate publishing workflows from PAT to GitHub App auth #429 (open) tracks migrating publishing workflows from PAT to GitHub App auth and flags the Admin-role bypass as an over-broad escape hatch.

Verdict on the premise

A rework is warranted, but not for the reason of "races we haven't patched yet" — those particular races have been patched. It's warranted because:

  1. The patches (fix(client): add missing is_archived field to UpdateProductRequest #236, fix(openapi): Align manufacturing_orders request body schema with Katana API #237, fix(release): serialize release-client and release-mcp #1001) are each correct locally while leaving the generating topology intact: N jobs each pushing to main inside a workflow that triggers on pushes to main. Every future package added to a repo re-opens the same class of bug.
  2. Two of the four repos have no working release pipeline at all. Wiring credentials into the existing design would be spending the migration budget on the design we want to leave.
  3. The failure that actually reached users — statuspro MCP-04: Package and Deploy MCP Server to PyPI #63's uninstallable MCP package — is a correctness failure of inter-package coupling, not a concurrency failure, and none of the three patches touches it.
  4. Artifacts are being permanently lost (mcp-v0.16.0 has no .mcpb, forever).

2. Repo inventory

katana stocktrim statuspro frontapp
Publishable artifacts client (PyPI), MCP (PyPI), MCP Docker (GHCR) client (PyPI), MCP (PyPI), .mcpb bundle (release asset) client (PyPI), TS client (npm), MCP (PyPI), MCP Docker (GHCR), MCPB client (PyPI), TS client (npm), MCP (PyPI)
Versioning independent independent independent independent
Tag formats client-v*, mcp-v* client-v*, mcp-v* client-v*, mcp-v*, ts-v* client-v*, mcp-v*, ts-v*
Python releaser PSR 9.x, per-package [tool.semantic_release], scope-filtered commit parser PSR 9.15.2, same PSR, same PSR, same
JS releaser semantic-release via packages/statuspro-client/.releaserc.json, tagFormat: ts-v${version}, npmPublish: false same shape
Working today? yes yes (with patches) no (bad token) no (triggers disabled)

Important compatibility observation: every existing tag is of the form <component>-v<version>. release-please's default tag format when include-component-in-tag: true is exactly <component>-v<version> with - as separator — so existing tags remain valid and continuity of version history is preserved without custom tag templating. (Inferred from release-please's manifest/component documentation; verify empirically on the pilot repo before rolling out.)

Versioning is already fully independent per package via scope-filtered commit parsers ((client) / (mcp) / (ts)). That convention transfers directly to release-please, which partitions commits by path rather than scope — a behavioural difference worth calling out (see §4 downsides).


3. What the research says

release-please (manifest mode) — the recommended target

  • With release-please-config.json + .release-please-manifest.json, release-please raises a single combined Release PR covering every configured package: "the motivation of the manifest-based releaser is support for monorepos… a combined Release PR will be created for all configured packages" (manifest-releaser.md). Merging that one PR bumps versions and changelogs for all changed packages in one commit, and release-please tags each package at that commit — all tags land together.
  • Release-please explicitly "does not handle publication to package managers" (README) — it updates changelogs, tags, and creates GitHub Releases. Your build/publish jobs are separate and are meant to trigger on the tag/release it produces.
  • First-class Python support: understands pyproject.toml, setup.py, setup.cfg, version.py, <project>/__init__.py, plus changelog generation.
  • Inter-package pinning and lockfile resync are NOT built in. The intended mechanisms are extra-files (with typed toml/json/yaml variants and jsonpath-style selectors) or a custom manifest plugin in the plugins array. Known friction: extra-files not reliably updated in some cases (release-please-action#879); updating files outside a package's own path in monorepos (release-please#2477).

python-semantic-release — the incumbent

  • PSR has no native multi-package/workspace concept. Even after the v10.4.0 "Conventional Commits Monorepo Parser," there is no workspace-level config: each package needs its own pyproject.toml with its own [tool.semantic_release], its own tag_format, its own scope and path filters, and you invoke the CLI once per package directory (PSR monorepo guide).
  • So the current one-job-per-package pattern is the only topology PSR offers for monorepos — but it is documented nowhere as concurrency-safe. The official monorepo doc doesn't address parallel invocations at all. PSR has also had scope-filtering gaps causing incorrect cross-package bumps (PSR#1215).
  • Upstream JS semantic-release (PSR's design ancestor) has open issues describing precisely the observed failures: parallel per-component jobs where the first push succeeds and the rest fail because the branch moved (semantic-release#1628); concurrent same-repo runs breaking because git fetch/push can't be done concurrently by multiple writers (#1545). The community's own recommendation is concurrency: groups or splitting version-generation from publishing (discussion#4103).

Answering the question directly: no, the current one-job-per-package topology is not a supported-safe topology. It is a documented workaround that inherits same-branch race risk unless you serialize it yourself — which is exactly what #1001 and #236 did, by hand, per repo.

Changesets

Architecturally very close to release-please (aggregate "Version Packages" PR → merge → publish), and it does natively resolve internal workspace dependency bumps — the thing release-please makes you script (changesets/action, automating-changesets.md). But it is npm/JS-workspace-specific with no Python awareness. Adopting it would mean running two independent release systems in statuspro/frontapp and none of the benefit in katana/stocktrim. Rejected.

The general principle

Do version-bump + tag exactly once, via a merged PR rather than a direct push from a job matrix; trigger the actual build+publish from tag or release events, not push: main; scope a concurrency: group to workflow+ref. This simultaneously eliminates (a) concurrent pushes racing on one branch, (b) release commits re-triggering on: push: main, and (c) the immutable-release asset problem — because the publish job now owns asset creation end-to-end instead of appending to a release someone else already finalized.


4. Recommendation

Primary: release-please in manifest mode, one config per repo, tag-triggered publishing

Why this one:

  1. It removes the generating condition rather than each symptom. Nothing pushes to main during a release. The version bump, the changelog, the MCP→client pin bump, and the uv.lock re-sync all happen on the release PR's branch, and land as one merge commit. There is no second push to race with, and no bot commit on main to re-trigger anything.
  2. It scales to N packages. Adding the TS client, a Docker image, or a fifth artifact doesn't add another writer to main.
  3. You already run it. harness-kit and AirHound use release-please, and the dougborg-release-please GitHub App (ID 4392719) is installed org-wide with credentials already wired into all four repos — confirmed as the ruleset bypass actor on katana and statuspro. Migration cost is mostly config, not new infrastructure or new secrets.
  4. It works with branch protection instead of around it. release-please's normal mode of operation is opening a PR. On katana/statuspro (required-PR rulesets), that removes the dependency on bypass entirely for the version-bump step — a direct improvement to the posture katana Migrate publishing workflows from PAT to GitHub App auth #429 is worried about. Bypass is then needed only if you want the release PR to auto-merge without approval.
  5. Existing tag names carry over (client-v* / mcp-v* / ts-v* match release-please's default component tag format), so version history is continuous.
  6. One review surface. Today a release is invisible until it has already happened. Under release-please, the pending release — every package, every version, every changelog entry — is a PR you can look at before it ships.

Honest downsides:

  • Path-based, not scope-based, change detection. PSR currently partitions by commit scope ((client) vs (mcp)). release-please partitions by path. A commit touching both trees will bump both packages. That's arguably more correct, but it is a behaviour change and will produce different bump decisions than today. It also means the commit-scope convention becomes advisory rather than load-bearing.
  • Inter-package pin bumps are hand-written glue. release-please will not natively bump mcp/pyproject.toml's client>=X when the client releases. You must use extra-files (with the known reliability caveats above) or a small step on the release-PR branch. This is genuinely the weakest part of the recommendation — Changesets does this natively and release-please does not.
  • uv.lock needs a job. release-please cannot run uv lock. You need a workflow that reacts to the release PR branch, runs uv lock, and commits back to that branch. Cheaper and far safer than today's push-to-main sync-lockfile job, but it is still glue you own.
  • Draft-release dance for assets. See §5.4 — not hard, but not free either.
  • Releases become batched, not continuous. Today a merge to main ships immediately. Under release-please, it ships when you merge the release PR. For these repos (internal API clients) that is a feature; if you want continuity, enable auto-merge on the release PR and you're back to roughly the current latency with none of the races.

Runner-up (rejected): keep PSR, restructure to "one version job, then tag-triggered publish"

Concretely: collapse release-client + release-mcp into a single job that runs the PSR CLI once per package directory sequentially, with push.ci disabled, then does exactly one git push --atomic origin main --tags at the end; add paths-ignore/actor guards so bot commits don't re-trigger; move all publishing (publish-*-pypi, Docker, .mcpb) into separate workflows triggered on: push: tags.

Why it's attractive: far cheaper migration, keeps a tool the repos already configure correctly, keeps scope-based partitioning, and would genuinely fix failures 1–4. If the appetite for change is low, this is a defensible place to stop — I'd rate it a clear improvement over today.

Why I rejected it as primary:

  • It still requires bot pushes directly to main, which means it still depends on ruleset bypass on katana/statuspro — moving in the opposite direction from Migrate publishing workflows from PAT to GitHub App auth #429.
  • Self-triggering is suppressed by guards (paths-ignore, actor checks) rather than by construction. Guards are one careless edit away from regressing, and paths-ignore interacts badly with required status checks.
  • PSR still has no cross-package model, so the MCP→client pin and uv.lock remain bespoke steps mutating main — the failure that actually shipped a broken package (statuspro MCP-04: Package and Deploy MCP Server to PyPI #63) stays structurally possible.
  • It leaves four repos on a topology upstream describes as a workaround, while a supported topology is already deployed elsewhere in the org.

5. Target topology

Pseudo-YAML; illustrative, not for copy-paste.

5.1 Config

release-please-config.json:

{
  "separate-pull-requests": false,          // ONE aggregated release PR
  "packages": {
    ".":                    { "release-type": "python", "component": "client",
                              "changelog-path": "docs/CHANGELOG.md" },
    "katana_mcp_server":    { "release-type": "python", "component": "mcp",
                              "extra-files": [                      // MCP -> client pin
                                { "type": "toml", "path": "katana_mcp_server/pyproject.toml",
                                  "jsonpath": "$.project.dependencies" } ] },
    "packages/xxx-client":  { "release-type": "node",   "component": "ts" }   // statuspro/frontapp only
  },
  "include-component-in-tag": true          // -> client-vX.Y.Z, mcp-vX.Y.Z, ts-vX.Y.Z
}

.release-please-manifest.json is seeded with today's live versions so history is continuous.

5.2 Workflow A — release-please.yml (the only thing watching main)

on:
  push: { branches: [main] }
concurrency: { group: release-please, cancel-in-progress: false }
jobs:
  release-please:
    steps:
      - googleapis/release-please-action@<sha>   # App token, ID 4392719
        # Creates/updates ONE release PR, or — if the release PR was just merged —
        # creates all tags + GitHub Releases at that single commit.
        # NOTE: this job NEVER pushes to main. It only writes to the release-PR branch.

This workflow re-triggering itself is harmless and expected: after the release PR merges it runs once more and tags. It performs no push to main, so there is no cascade.

5.3 Workflow B — release-pr-prepare.yml (the glue, on the PR branch only)

on:
  pull_request: { branches: [main] }
jobs:
  sync:
    if: startsWith(github.head_ref, 'release-please--')
    steps:
      - checkout (ref: github.head_ref, App token)
      - run: uv lock                       # resync lockfile to the bumped versions
      - run: scripts/sync-mcp-pin.py       # belt-and-braces for the MCP -> client floor,
                                           # in case extra-files under-delivers (#879)
      - commit + push to the RELEASE PR BRANCH   # <-- never to main

This is where katana's sync-lockfile job and statuspro/frontapp's update-mcp-dependency.yml go to die. Their work still happens; it just happens somewhere harmless, and lands atomically with the version bump instead of as a trailing commit on main.

5.4 Workflow C — publish.yml (tag-driven; the only thing that builds and ships)

on:
  push: { tags: ['client-v*', 'mcp-v*', 'ts-v*'] }
concurrency: { group: publish-${{ github.ref }}, cancel-in-progress: false }
jobs:
  publish-client:   # if: startsWith(github.ref_name, 'client-v')
    environment: pypi-client                # PyPI trusted publishing (OIDC), unchanged
    steps: [ build sdist+wheel, pypa/gh-action-pypi-publish, gh release upload ]

  publish-mcp:      # if: startsWith(github.ref_name, 'mcp-v')
    environment: pypi-mcp
    steps:
      - build wheel + .mcpb bundle          # ALL assets built BEFORE the release is finalised
      - pypa/gh-action-pypi-publish
      - gh release upload ${{ github.ref_name }} dist/* *.mcpb   # release is still DRAFT here
      - gh release edit ${{ github.ref_name }} --draft=false      # publish -> becomes immutable

  publish-mcp-docker:   # if: startsWith(github.ref_name, 'mcp-v')
    needs: publish-mcp
    steps: [ buildx, push ghcr.io/... :${{ version }} and :latest ]

  publish-ts:       # if: startsWith(github.ref_name, 'ts-v')   (statuspro/frontapp)
    steps: [ npm publish --provenance ]     # OIDC, unchanged

On immutability: set "draft": true in the release-please config so release-please creates draft releases. Assets are attached while draft; gh release edit --draft=false publishes, and only then does immutability lock the release. This is the pattern GitHub documents, and it generalises stocktrim #237's fix (create-with-assets) to the case where the release creator and the asset builder are different jobs — which is unavoidable once release-please owns release creation.

5.5 What gets deleted

  • katana release.yml's release-client, release-mcp, sync-lockfile jobs — and release-mcp.yml merges into publish.yml
  • stocktrim release.yml's release-client, release-mcp jobs
  • statuspro/frontapp release.yml, release-ts.yml, update-mcp-dependency.yml; release-mcp.yml folds into publish.yml
  • every [tool.semantic_release] block, every .releaserc.json
  • SEMANTIC_RELEASE_TOKEN becomes unnecessary — which is, not incidentally, the exact secret whose absence has kept statuspro and frontapp broken

5.6 Ruleset interaction (no settings changes proposed here)

Repo Behaviour under the new design
katana Required-PR ruleset is satisfied by construction — release-please opens a PR. App bypass (4392719) becomes optional, needed only for auto-merge. Net reduction in privilege, aligned with #429.
statuspro Same as katana.
stocktrim No PR requirement today; release-please's PR flow works regardless. Linear history is satisfied since the release PR is a single squash/rebase merge — the pull --rebase gymnastics from #236 disappear.
frontapp Unprotected. Works as-is; would be a good moment to add a ruleset matching the others, but that's out of scope for this proposal.

Explicitly: no ruleset, secret, variable, or repo-setting change is proposed. The App and its credentials are already in place.


6. Migration path

Pilot: frontapp-openapi-client. Counter-intuitive but correct — its release pipeline is already disabled and has never worked, so there is literally nothing to break. It also has no branch ruleset, so no protection interactions to debug, and it exercises the full matrix (Python client + TS client + MCP + inter-package pin). Its issue #10 currently proposes re-enabling the old design once tokens are configured; the right resolution is to migrate instead, and close #10 as superseded.

Order:

  1. frontapp — greenfield pilot. Validates: tag-format continuity, extra-files pin bumping, the uv lock on-PR-branch job, the draft-release asset dance, TS+Python coexistence. Fix the latent >=0.51.0 pin bug in passing.
  2. statuspro — structurally identical, also currently broken. Copy the validated config. Adds the MCPB bundle and GHCR Docker to the validated set. Removes the need to ever debug the SEMANTIC_RELEASE_TOKEN failure.
  3. stocktrim — first repo with a working pipeline to migrate, but the simplest one (2 Python packages, no TS, no required-PR ruleset). Also the repo carrying the two most recent band-aids.
  4. katana — last, because it's the most complex (3 artifacts incl. GHCR), the highest-traffic, and the only one with a genuinely nontrivial existing sync-lockfile dependency.

Incremental steps available before any big-bang — each is independently valuable and reversible:

  • Step 0 (do this regardless, any repo): move publishing out of the release workflow onto on: push: tags. This alone decouples build/publish from versioning and can be done while PSR still owns versioning.
  • Step 0.5 (katana, cheap): apply the fix from Release automation leaves root uv.lock behind the package pyproject version #901's comment that was never applied — fold uv lock into PSR's build_command and add uv.lock to assets. This retires the sync-lockfile job and one push-to-main per release today, independent of any migration.
  • Step 1: land release-please config + manifest in a repo alongside PSR, with release-please's release creation disabled, and compare the release PR it proposes against what PSR actually did for a few cycles. Zero risk, high confidence gain.
  • Step 2: cut over — disable PSR, enable release-please releases, add tag-triggered publish.

Band-aid disposition:

Fix Fate under the new design
stocktrim #236 (ref: main, reset --hard, pull --rebase) Redundant. No release job checks out or pushes main.
stocktrim #237 (vcs_release: false, gh release create with asset) Principle retained, implementation replaced. "Build all assets before finalising the release" survives as the draft→attach→publish flow in §5.4; the PSR-specific vcs_release: false flag disappears with PSR.
katana #1001 (serialize release-mcp after release-client) Redundant. There is only one version-producing step, and it produces one commit.
katana sync-lockfile job Redundant (superseded by the on-PR-branch uv lock job). Retireable earlier via Step 0.5.
statuspro/frontapp update-mcp-dependency.yml Redundant (folds into extra-files + the on-PR-branch job).

7. Open questions

  1. Confirm empirically that release-please's include-component-in-tag produces exactly client-vX.Y.Z / mcp-vX.Y.Z and picks up existing tags for changelog ranges. (Inferred from docs; must be verified on the pilot.)
  2. How reliable is extra-files for a TOML dependency-array edit specifically? Given feat(mcp): surface landed-cost apportionment at receive time — accept additional_costs on receive_purchase_order.items[] (or document the modify path) #879, plan for the scripted fallback in §5.3 from day one.
  3. Does the path-based → scope-based partitioning change produce materially different bump decisions on these repos' commit history? Worth a dry-run comparison during Step 1.
  4. Should the release PR auto-merge? Auto-merge preserves today's ship-on-merge latency but gives up the review surface that is one of the main benefits.

Sources

Metadata

Metadata

Assignees

No one assigned

    Labels

    ci-cdCI/CD workflows and automationenhancementNew feature or requestgithub-actionsGitHub Actions workflows and automationp1-highHigh priorityreleaseRelease tasks

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions