Release-please migration validation¶
Recorded 2026-10-03 against main 7432169960f486040ac6ef23c3e355b9f4bea01e
and these exact reviewed implementation snapshots:
- #655 adapters:
9952967b12a6b06270710fb4e05a6b91199f7499. - #656 configuration/workflows:
8b4dee606d7b556af55dc54a1d93c93d1d5106c7.
The recorded checks below apply to those immutable revisions; PR numbers alone are not validation evidence. Reruns must record their new tested SHAs. The migration changes no published package version: the repository remains 1.8.0 until a separate reviewed release PR is merged.
Executed checks¶
- 109 focused offline tests passed: canonical composer, lifecycle reconciliation, release workflow contracts, release detection, legacy preparer and summary.
- Ruff check/format and
git diff --checkpassed for changed Python files. - Configuration and manifest validated against installed release-please 17.6.0
schemas. Upstream action v5.0.0 SHA
45996ed1f6d02564a971a2fa1b5860e934307cf7bundles that core version. - Actual core Python strategy executed with synthetic commits and mocked local SCM reads: fix→1.8.1, feat→1.9.0, breaking→2.0.0, docs→1.8.1, chore-only→no candidate, Release-As override→1.9.0.
- Actual generated feature output is captured in
test/fixtures/release-please/17.6.0-feature.md. It composed against current main's full curated notes, preserving historical sections byte for byte. The initial generated file must be empty: a nonempty heading-only seed is demoted into an unsupported extra heading by the upstream first updater. - Isolated synthetic 1.9.0 candidate passed
make release-check VERSION=1.9.0: canonical version/notes checks, sdist/wheel build and twine check. - Fresh independent virtual environments installed the candidate wheel and sdist; package import and distribution metadata both reported 1.9.0. The wheel's SQLAlchemy dialect entry point was present. Nothing was uploaded.
- Independent architecture and postimplementation agent reviews approved the bounded migration, including regeneration freeze, lifecycle ownership, unchanged publisher gates and explicit unverified paths.
Limits¶
SCM data in upstream strategy execution was mocked. This is real pinned-core candidate generation, not a live GitHub API/bot PR rehearsal. No release PR, main merge, tag, GitHub Release, PyPI upload or credential creation occurred. No live database compatibility matrix or candidate cookbook run was executed. Existing publisher dry-run evidence in RELEASING.md covers unchanged publisher code; it installs a published package and does not prove the fresh candidate artifact's database/cookbook behavior (superseded by the candidate-gate dry run below). Required CI must pass at the final heads.
Live evidence since the migration¶
The checks above are the 2026-10-03 offline record and stay unchanged. Live publication and dry-run evidence since then (the history of each release pull request, including the open candidate, is on #651 and #654):
| Evidence | Revision | Result |
|---|---|---|
| First live release PR #661 (1.9.0) | final head d221ccf; merge 4d41a22 |
One release PR from manifest 1.8.0; the diff touched only the manifest, CHANGELOG.md, RELEASE_CHANGELOG.md and __version__. The pull request runs created by bot updates started no jobs (they were later recorded as failed) until a maintainer closed and reopened the PR; the checks then passed at the final head. |
| Publication of 1.9.0, run 37209737260 | 4d41a22 |
Every job passed, including the live PyPI Trusted Publishing exchange and cookbook verification; reconciliation moved #661 to autorelease: tagged. |
| Publication of 1.10.0, run 37871732934 | 57850be |
Attempt 1 failed only in post-publication cookbook verification (PyPI served stale metadata for a short time); re-running the failed jobs passed without publishing again. Reconciliation then moved #704 to autorelease: tagged. |
| No-publication dry run with the candidate gate, run 38156819662 | 2f1083d (#814) |
The cookbook verified the freshly built 1.10.0 wheel before publish; publish was skipped and nothing was published. See RELEASING.md "Dry-run evidence". |
Still not exercised live, covered only by the offline release tests: a failed
required check that prevents publication of a merged release, a partial upload
finished by gh run rerun --failed, a resume or verify-only dispatch, and
the manual relabeling after recovery at another SHA. A review of the runbook by
a maintainer other than its author is not recorded either.
Reproduce the pinned-core scenarios¶
Install the pinned core at the exact path used by this script:
The following script uses the real Python strategy and local file-backed mocked SCM reads.
Save it outside the checkout, then run node <script> <repository-path>.
Its output candidate lives in /tmp/sa-rp-candidate, not in the repository.
const fs=require('fs'),path=require('path');
const upstream='/tmp/release-please-tools/node_modules/release-please/build/src';
require(upstream+'/index.js'); const {Python}=require(upstream+'/strategies/python.js');
const {Manifest}=require(upstream+'/manifest.js');
const {parseConventionalCommits}=require(upstream+'/commit.js');
const {TagName}=require(upstream+'/util/tag-name.js');
const {Version}=require(upstream+'/version.js');
const root=process.argv[2];
const github={repository:{owner:'cubrid-lab',repo:'sqlalchemy-cubrid'},getFileJson:async p=>JSON.parse(fs.readFileSync(path.join(root,p),'utf8')),findFilesByFilenameAndRef:async()=>[],getFileContentsOnBranch:async p=>({parsedContent:fs.readFileSync(path.join(root,p),'utf8')})};
(async()=>{
const manifest=await Manifest.fromManifest(github,'main');
const released=/^__version__ = "([^"]+)"$/m.exec(fs.readFileSync(path.join(root,'sqlalchemy_cubrid/__init__.py'),'utf8'))[1];
if(manifest.releasedVersions['.'].toString()!==released||manifest.repositoryConfig['.'].releaseType!=='python')throw new Error('Invalid manifest/config mapping');
const scenarios=[['fix: correct failure','1.8.1','Fixed'],['feat: new optional API','1.9.0','Added'],['feat!: remove old API\n\nBREAKING CHANGE: remove old API','2.0.0','Added'],['docs: improve instructions','1.8.1','Documentation'],['perf: faster compile','1.8.1','Performance'],['chore: housekeeping',null],['ci: pin action',null],['test: add case',null],['refactor: tidy',null],['fix: explicit override\n\nRelease-As: 1.9.0','1.9.0','Fixed']];
const allowed=new Set(['Upgrade notes','Added','Changed','Deprecated','Removed','Fixed','Security','Performance','Documentation','CI','Tests','⚠ BREAKING CHANGES']);
for(const [message,expected,heading] of scenarios){
const strategy=new Python({...manifest.repositoryConfig['.'],github,targetBranch:'main'});
const commits=parseConventionalCommits([{sha:'a'.repeat(40),message,files:['sqlalchemy_cubrid/__init__.py']}]);
const candidate=await strategy.buildReleasePullRequest(commits,{tag:new TagName(Version.parse('1.8.0')),sha:'78bcbc7dbd70d8f2c756b25d3c271977ace42aa5',notes:''});
const actual=candidate?candidate.version.toString():null;
if(actual!==expected)throw new Error(`${message}: expected ${expected} got ${actual}`);
const headings=candidate?[...candidate.body.toString().matchAll(/^### (.+)$/gm)].map(m=>m[1]):[];
if(candidate&&(!headings.includes(heading)||headings.some(h=>!allowed.has(h))))throw new Error(`${message}: expected ### ${heading}, got ${headings}`);
console.log(JSON.stringify({message,version:actual,headings}));
if(candidate&&message.startsWith('feat:')){
const out='/tmp/sa-rp-candidate';fs.mkdirSync(out,{recursive:true});
for(const update of candidate.updates){
const file=path.join(root,update.path); if(!fs.existsSync(file)&&!update.createIfMissing) continue;
const content=update.updater.updateContent(fs.existsSync(file)?fs.readFileSync(file,'utf8'):undefined);
fs.mkdirSync(path.dirname(path.join(out,update.path)),{recursive:true}); fs.writeFileSync(path.join(out,update.path),content);
console.log('updated '+update.path);
}
}
}
})().catch(err=>{console.error(err);process.exit(1)});
The script reads release-please-config.json and .release-please-manifest.json
through the upstream manifest loader, so the repository's changelog-sections
apply. It fails unless the manifest version equals __version__ in
sqlalchemy_cubrid/__init__.py, and it checks the ### heading each releasable
commit type produces (AGENTS.md "GitHub Release Policy"): fix→Fixed, feat→Added,
docs→Documentation, perf→Performance; chore, ci, test and refactor alone create
no candidate. The feature candidate's RELEASE_CHANGELOG.md, generated from an
empty file, is the test/fixtures/release-please/17.6.0-feature.md fixture
(with the fixture's recorded date).