Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/scripts/check-release-intent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ EOF
exit 1
fi

consumer_products_json=''
if [[ "${is_release_pr}" == true ]]; then
base_commit="$(git rev-parse "${base_ref}^{commit}")"
head_parent="$(git rev-parse "${head_ref}^{commit}^")"
Expand All @@ -147,6 +148,12 @@ if [[ "${is_release_pr}" == true ]]; then
bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "${head_ref}" tools/dev/bun.sh tools/release/verify-release-commit.mts \
--products-json "${release_products_json}" \
--head-ref "${head_ref}"
consumer_products_json="${release_products_json}"
elif [[ "${event_name}" == workflow_dispatch && "${full_ref}" == refs/heads/main && "${CI_RELEASE_PRODUCTS_JSON:-[]}" != '[]' ]]; then
consumer_products_json="${CI_RELEASE_PRODUCTS_JSON}"
fi
if [[ -n "${consumer_products_json}" ]]; then
bash tools/release/with-product-history.sh "$PWD" "${head_ref}" '' @workspace bash tools/dev/bun.sh tools/release/consumer-compatibility.mts "${consumer_products_json}"
fi

release_plan="$(bash tools/release/release-plan.sh --base-ref "${base_ref}" --head-ref "${head_ref}" --format json)"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ jobs:
HEAD_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
CI_EVENT_NAME: ${{ github.event_name }}
CI_FULL_REF: ${{ github.ref }}
CI_RELEASE_PRODUCTS_JSON: ${{ inputs.release_products_json || '[]' }}
run: |
subject="${PR_TITLE:-}"
if [ -z "$subject" ]; then
Expand Down
34 changes: 34 additions & 0 deletions src/docs/maintainers/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,40 @@ assembles release packages; the retired dry-run wrapper did not assemble them
either. Use the selected products' package and artifact/consumer test tasks for
local package rehearsal.

Release admission checks consumer combinations before uploading registry bytes.
The CI release-intent job runs the same compatibility gate on generated release
PRs, their main release commits and explicitly selected main qualification
dispatches; ordinary source PRs and feature diagnostics keep source qualification.
`consumer-compatibility.mts` reads selected consumers' declared pins and reads
unselected dependencies from their immutable product tags. It compares the WASIX
SDK runtime with the runtime embedded by its exact N-API dependency, follows
declared SDK dependencies through tools and React Native, checks consumers'
own runtime pins against those SDKs, and checks every
external extension against consumers that enforce exact runtime identity.
Cargo SDK admission also checks published, unselected extension facades' API
requirements. A workspace `qualificationOnly` fixture cannot satisfy these
release contracts. A new runtime SDK must declare its extension compatibility
policy before metadata admission.

Cargo extension facades use compatible SemVer requirements for SDK, build-helper
and binding APIs; extension payload and AOT carrier versions remain exact.
The shared facade generator is a release source of every external extension and
the native runtime product that owns the contrib facade, so Release Please
includes those products when its shipped output changes. WASIX tools declare
their SDK pin explicitly instead of deriving it from the current workspace SDK.
These rules preserve independent packaging versions; they do not establish
cross-runtime extension compatibility or waive ABI, AOT, export or integrity
checks. Incompatible combinations require a fresh matching release candidate.

Freezing the publication candidate additionally checks the unmodified WASIX SDK
tarball's declared pins and every selected N-API tarball, including macOS carriers
on a Linux preparation host. Packing and atomically installing a frozen candidate
repeat that check, so approval-run reuse and bootstrap retries cannot bypass it.
The post-publication npm check validates the actual
installed carrier for every installed WASIX SDK, including unselected historical
SDKs and nested dependency installations. Source qualification and installed
release compatibility remain separate requirements.

For a release packaging failure, download the failed candidate's inputs from its
exact CI run into an isolated checkout and run the same assembly entrypoint:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ export function writeFacadeSource(product, outputRoot, { dependencyPaths = {} }
`[target.'cfg(${cfg})'.dependencies]\n${name} = { version = "=${version}", optional = true${dependencyPaths[name] ? `, path = ${JSON.stringify(dependencyPaths[name])}` : ''} }`,
);
}
const dependency = (name, version, optional = true) =>
`${name} = { version = "=${version}"${optional ? ', optional = true' : ''}, default-features = false${dependencyPaths[name] ? `, path = ${JSON.stringify(dependencyPaths[name])}` : ''} }`;
const dependency = (name, version, exact = true, optional = true) =>
`${name} = { version = "${exact ? '=' : '^'}${version}"${optional ? ', optional = true' : ''}, default-features = false${dependencyPaths[name] ? `, path = ${JSON.stringify(dependencyPaths[name])}` : ''} }`;
const cfgForTriple = (triple) =>
rustNativeTargetCfg(
{
Expand All @@ -162,12 +162,16 @@ export function writeFacadeSource(product, outputRoot, { dependencyPaths = {} }
const sdkVersion = currentProductVersionSync('oliphaunt-rust');
const bindingVersion = currentProductVersionSync('liboliphaunt-native-bindings');
const optionalDependencies = [
dependency('liboliphaunt-native-bindings', bindingVersion),
dependency('liboliphaunt-native-bindings', bindingVersion, false),
...(wasixName === null
? []
: [
dependency(wasixName, version),
dependency('oliphaunt-wasix', currentProductVersionSync('oliphaunt-wasix-rust')).replace(
dependency(
'oliphaunt-wasix',
currentProductVersionSync('oliphaunt-wasix-rust'),
false,
).replace(
'default-features = false',
'default-features = false, features = ["extensions"]',
),
Expand Down Expand Up @@ -206,7 +210,7 @@ ${features.join('\n')}
${optionalDependencies}

[build-dependencies]
${dependency('oliphaunt-build', sdkVersion)}
${dependency('oliphaunt-build', sdkVersion, false)}

${targetDependencies.join('\n\n')}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, test } from 'bun:test';
import { createHash } from 'node:crypto';
import {
cpSync,
mkdirSync,
mkdtempSync,
readdirSync,
Expand All @@ -9,6 +10,7 @@ import {
statSync,
writeFileSync,
} from 'node:fs';
import { packagedCargoManifestText } from '../../../../../tools/packaging/cargo-source-package.mts';
import { tmpdir } from 'node:os';
import path from 'node:path';
import {
Expand Down Expand Up @@ -49,22 +51,37 @@ if (['prepare-compiler', 'verify-compiler'].includes(process.argv[2])) {
'package-extension-cargo-facades.test',
);
const products = ['oliphaunt-extension-contrib-pg18', 'oliphaunt-extension-vector'];
const helperSource = path.resolve(
import.meta.dir,
'../../../../native/sdks/rust/crates/oliphaunt-build',
);
const helper = path.join(root, 'build-helper');
cpSync(helperSource, helper, { recursive: true });
const nextPatch = (version) => {
const parts = version.split('.');
parts[2] = String(Number(parts[2]) + 1);
return parts.join('.');
};
writeFileSync(
path.join(helper, 'Cargo.toml'),
packagedCargoManifestText(readFileSync(path.join(helper, 'Cargo.toml'), 'utf8')).replace(
/^version = "[^"]+"/mu,
`version = "${nextPatch(currentProductVersionSync('oliphaunt-rust'))}"`,
),
);
const dependencyPaths = {
'liboliphaunt-native-bindings': path.resolve(
import.meta.dir,
'../../../../native/rust-bindings',
),
'oliphaunt-build': path.resolve(
import.meta.dir,
'../../../../native/sdks/rust/crates/oliphaunt-build',
),
'oliphaunt-build': helper,
};
// The native consumer does not enable WASIX, but Cargo resolves optional coordinates.
const wasixStub = path.join(root, 'wasix-sdk');
mkdirSync(path.join(wasixStub, 'src'), { recursive: true });
writeFileSync(
path.join(wasixStub, 'Cargo.toml'),
`[package]\nname = "oliphaunt-wasix"\nversion = "${currentProductVersionSync('oliphaunt-wasix-rust')}"\nedition = "2024"\n[features]\nextensions = []\n[workspace]\n`,
`[package]\nname = "oliphaunt-wasix"\nversion = "${nextPatch(currentProductVersionSync('oliphaunt-wasix-rust'))}"\nedition = "2024"\n[features]\nextensions = []\n[workspace]\n`,
);
writeFileSync(path.join(wasixStub, 'src/lib.rs'), '');
dependencyPaths['oliphaunt-wasix'] = wasixStub;
Expand Down Expand Up @@ -180,7 +197,7 @@ fixture-native-tools = { path = ${JSON.stringify(tools)} }
fixture-broker = { path = ${JSON.stringify(broker)} }

[build-dependencies]
oliphaunt-build = { path = ${JSON.stringify(path.resolve(import.meta.dir, '../../../../native/sdks/rust/crates/oliphaunt-build'))} }
oliphaunt-build = { path = ${JSON.stringify(helper)} }

[workspace]
`,
Expand Down Expand Up @@ -294,6 +311,18 @@ describe('exact extension Cargo facade', () => {
'dep:oliphaunt-extension-pgtap-wasix',
]);
expect(pkg.cratePath.endsWith('.crate')).toBe(true);
expect(manifest.dependencies['oliphaunt-wasix'].version).toBe(
`^${currentProductVersionSync('oliphaunt-wasix-rust')}`,
);
expect(manifest.dependencies['liboliphaunt-native-bindings'].version).toBe(
`^${currentProductVersionSync('liboliphaunt-native-bindings')}`,
);
expect(manifest['build-dependencies']['oliphaunt-build'].version).toBe(
`^${currentProductVersionSync('oliphaunt-rust')}`,
);
expect(manifest.dependencies['oliphaunt-extension-pgtap-wasix'].version).toBe(
`=${pkg.version}`,
);
});

test('the native-owned contrib facade has no WASIX carrier dependency', () => {
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/pg_hashids/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-pg-hashids"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/pg_ivm/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-pg-ivm"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/pg_textsearch/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-pg-textsearch"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/pg_uuidv7/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-pg-uuidv7"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/pgtap/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-pgtap"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/postgis/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-postgis"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/extensions/external/vector/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "oliphaunt-extension-vector"
owner = "@oliphaunt/extensions"
kind = "exact-extension-artifact"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = [
"github-release-assets",
"npm",
Expand Down
1 change: 1 addition & 0 deletions src/native/runtime/release.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
id = "liboliphaunt-native"
owner = "@oliphaunt/core"
kind = "native-core"
shared_source_paths = ["src/extensions/artifacts/packages/tools/package-extension-cargo-facades.mts"]
publish_targets = ["github-release-assets", "npm", "maven-central", "crates-io"]
registry_packages = [
"crates:liboliphaunt-native-linux-arm64-gnu",
Expand Down
5 changes: 5 additions & 0 deletions src/wasix/postgres-tools/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ parser = "json:oliphaunt.runtimeVersion"
source_product = "liboliphaunt-wasix"
path = "src/wasix/postgres-tools/npm/package.json"
parser = "json:oliphaunt.runtimeVersion"

[compatibility_versions.oliphaunt-wasix-tools-sdk]
source_product = "oliphaunt-wasix-ts"
path = "src/wasix/postgres-tools/ts/package.json"
parser = "json:oliphaunt.wasixSdkVersion"
3 changes: 2 additions & 1 deletion src/wasix/postgres-tools/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
},
"oliphaunt": {
"runtimeProduct": "liboliphaunt-wasix",
"runtimeVersion": "0.3.1"
"runtimeVersion": "0.3.1",
"wasixSdkVersion": "0.2.1"
},
"exports": {
".": {
Expand Down
14 changes: 6 additions & 8 deletions src/wasix/postgres-tools/ts/tools/package.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ const SOURCE = path.join(ROOT, 'src/wasix/postgres-tools/ts');
const CARRIER = '@oliphaunt/liboliphaunt-wasix-tools';
const BINDING = '@oliphaunt/wasix-ts';

export function prepareWasixToolsTypescriptPackage(packageDir, bindingVersion) {
if (!/^\d+\.\d+\.\d+$/u.test(bindingVersion)) throw new Error('binding version must be exact');
export function prepareWasixToolsTypescriptPackage(packageDir) {
const manifestFile = path.join(packageDir, 'package.json');
const manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
const bindingVersion = manifest.oliphaunt?.wasixSdkVersion;
if (!/^\d+\.\d+\.\d+$/u.test(bindingVersion)) throw new Error('binding version must be exact');
const runtimeVersion = manifest.oliphaunt?.runtimeVersion;
if (!/^\d+\.\d+\.\d+$/u.test(runtimeVersion)) throw new Error('runtime version must be exact');
manifest.dependencies = { [CARRIER]: manifest.version };
Expand All @@ -31,7 +32,7 @@ export function prepareWasixToolsTypescriptPackage(packageDir, bindingVersion) {
return manifest;
}

export function stageWasixToolsTypescriptPackage(outputDir, bindingVersion) {
export function stageWasixToolsTypescriptPackage(outputDir) {
const destination = path.resolve(ROOT, outputDir);
const relative = path.relative(ROOT, destination);
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
Expand All @@ -49,13 +50,10 @@ export function stageWasixToolsTypescriptPackage(outputDir, bindingVersion) {
path.join(ROOT, 'src/wasix/postgres-tools/CHANGELOG.md'),
path.join(destination, 'CHANGELOG.md'),
);
return prepareWasixToolsTypescriptPackage(destination, bindingVersion);
return prepareWasixToolsTypescriptPackage(destination);
}

if (import.meta.main) {
const output = process.argv[2] ?? 'target/oliphaunt-wasix-tools-ts/package';
const binding = JSON.parse(
readFileSync(path.join(ROOT, 'src/wasix/sdks/ts/package.json'), 'utf8'),
);
stageWasixToolsTypescriptPackage(output, binding.version);
stageWasixToolsTypescriptPackage(output);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ function fail(message) {
throw new Error(`${TOOL}: ${message}`);
}

export function prepareWasixToolsTypescriptPackage(packageDir, bindingVersion) {
export function prepareWasixToolsTypescriptPackage(packageDir) {
const root = path.resolve(packageDir);
const manifest = prepareProductPackage(root, bindingVersion);
const manifest = prepareProductPackage(root);
assertReleaseNoticesInDirectory(root, NOTICE_OPTIONS);
assertWasixToolsTypescriptManifest(manifest, `${PACKAGE_NAME} staged package`);
return manifest;
Expand All @@ -49,6 +49,7 @@ export function assertWasixToolsTypescriptManifest(manifest, label = PACKAGE_NAM
dependencies[TOOLS_CARRIER] !== manifest.version ||
JSON.stringify(Object.keys(peerDependencies)) !== JSON.stringify([WASIX_BINDING]) ||
!EXACT_VERSION.test(peerDependencies[WASIX_BINDING]) ||
manifest.oliphaunt?.wasixSdkVersion !== peerDependencies[WASIX_BINDING] ||
manifest.oliphaunt?.runtimeProduct !== 'liboliphaunt-wasix' ||
!EXACT_VERSION.test(manifest.oliphaunt?.runtimeVersion) ||
Object.keys(manifest.optionalDependencies ?? {}).length > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function manifest() {
oliphaunt: {
runtimeProduct: 'liboliphaunt-wasix',
runtimeVersion: '4.5.6',
wasixSdkVersion: '7.8.9',
},
dependencies: {
'@oliphaunt/liboliphaunt-wasix-tools': '1.2.3',
Expand All @@ -43,5 +44,8 @@ describe('WASIX TypeScript tools package contract', () => {
const extra = manifest();
extra.dependencies.other = '1.0.0';
expect(() => assertWasixToolsTypescriptManifest(extra)).toThrow(/exact WASIX binding/);
const mismatch = manifest();
mismatch.oliphaunt.wasixSdkVersion = '7.8.10';
expect(() => assertWasixToolsTypescriptManifest(mismatch)).toThrow(/exact WASIX binding/);
});
});
30 changes: 30 additions & 0 deletions src/wasix/sdks/ts/tools/wasix-typescript-package.mts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,36 @@ export function assertWasixTypescriptManifest(manifest, label = `${PACKAGE_NAME}
return manifest;
}

export function assertWasixTypescriptNativeCarrier(manifest, carrier) {
const sdk = manifest.oliphaunt;
const native = carrier.oliphaunt;
if (
manifest.name !== PACKAGE_NAME ||
sdk?.runtimeProduct !== 'liboliphaunt-wasix' ||
sdk?.wasixNapiProduct !== NATIVE_PRODUCT ||
!/^\d+\.\d+\.\d+$/u.test(sdk?.runtimeVersion ?? '') ||
!/^\d+\.\d+\.\d+$/u.test(sdk?.wasixNapiVersion ?? '') ||
!Number.isSafeInteger(sdk?.wasixAddonAbiVersion) ||
sdk.wasixAddonAbiVersion < 1 ||
!Number.isSafeInteger(sdk?.nodeApiVersion) ||
sdk.nodeApiVersion < 1 ||
Object.hasOwn(sdk, 'qualificationOnly') ||
Object.hasOwn(native ?? {}, 'qualificationOnly') ||
!NATIVE_PACKAGES.includes(carrier.name) ||
carrier.version !== sdk?.wasixNapiVersion ||
manifest.optionalDependencies?.[carrier.name] !== carrier.version ||
native?.runtimeProduct !== sdk?.runtimeProduct ||
native?.runtimeVersion !== sdk?.runtimeVersion ||
native?.addonAbiVersion !== sdk?.wasixAddonAbiVersion ||
native?.nodeApiVersion !== sdk?.nodeApiVersion ||
JSON.stringify(native?.profiles) !== JSON.stringify(['standard', 'icu'])
) {
fail(
`${carrier.name}@${carrier.version} is incompatible with ${PACKAGE_NAME}@${manifest.version}: SDK requires N-API ${sdk?.wasixNapiVersion} embedding runtime ${sdk?.runtimeVersion}, carrier embeds runtime ${native?.runtimeVersion}`,
);
}
}

export function prepareWasixTypescriptPackage(packageDir) {
const root = path.resolve(packageDir);
const manifest = prepareProductPackage(root);
Expand Down
Loading
Loading