Skip to content
Open
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
8 changes: 8 additions & 0 deletions .github/workflows/cli-package-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ on:
- 'packages/cli/src/runtime-host-peer-*'
- 'packages/cli/src/runtime-host-service-*'
- 'packages/runtime-host/package.json'
- 'packages/runtime-host/src/protocol/**'
- 'packages/runtime-host/src/client/peer-client.ts'
- 'packages/runtime-host/src/peer-mesh/**'
- 'packages/runtime-host/src/server/peer-listener.ts'
Expand All @@ -40,7 +41,11 @@ on:
- 'packages/storage/src/file-lifetime-owner.ts'
- 'packages/storage/src/native-file-lock.ts'
- 'scripts/generate-runtime-host-peer-*'
- 'scripts/release-cli-compatibility.mjs'
- 'scripts/release-cli-compatibility.test.mjs'
- 'scripts/release-cli-package.mjs'
- 'scripts/release-cli-websocket-smoke.mjs'
- 'scripts/release-cli-websocket-smoke.test.mjs'
- 'scripts/qualify-released-cli-state-root.mjs'
- 'scripts/qualify-released-cli-state-root.test.mjs'
- 'scripts/released-cli-state-root-fixture.mjs'
Expand Down Expand Up @@ -223,6 +228,7 @@ jobs:
- name: Build the release tarball once
env:
MAKA_CLI_NIGHTLY_VERSION: ${{ inputs.package_version }}
MAKA_RELEASE_SOURCE_COMMIT: ${{ inputs.source_commit || github.sha }}
MAKA_RUNTIME_HOST_PEER_PREBUILDS: ${{ runner.temp }}/runtime-host-peer-prebuilds
run: npm run release:cli:pack
- name: Upload the immutable release candidate
Expand Down Expand Up @@ -300,6 +306,8 @@ jobs:
- name: Validate the installed tarball
id: first-node-smoke
continue-on-error: true
env:
MAKA_RELEASE_SOURCE_COMMIT: ${{ inputs.source_commit || github.sha }}
run: node scripts/smoke-release-cli-package.mjs
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: matrix.second_node != ''
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/desktop-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ jobs:
shell: bash
env:
MAKA_DESKTOP_NIGHTLY_VERSION: ${{ needs.identity.outputs.version }}
MAKA_RELEASE_SOURCE_COMMIT: ${{ needs.identity.outputs.source_commit }}
steps:
- name: Reject in-place workflow reruns
if: github.run_attempt != 1
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ jobs:
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
environment: release
env:
MAKA_RELEASE_SOURCE_COMMIT: ${{ needs.release-identity.outputs.source_commit }}
timeout-minutes: 75
defaults:
run:
Expand Down Expand Up @@ -322,6 +324,8 @@ jobs:
needs: release-identity
runs-on: macos-15
environment: release
env:
MAKA_RELEASE_SOURCE_COMMIT: ${{ needs.release-identity.outputs.source_commit }}
timeout-minutes: 45
defaults:
run:
Expand Down
48 changes: 43 additions & 5 deletions apps/desktop/electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@
* under the License.
*/

import { execFileSync } from 'node:child_process';
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
resolveDesktopBuildVersion,
resolveRuntimeHostSetupPackage,
} from '../../scripts/desktop-nightly.mjs';
import { workspaceReleaseManifest } from '../../scripts/release-cli-file-policy.mjs';
import { resolveProductManifestIdentity } from '../../scripts/product-release-identity.mjs';
import { resolveMakaReleaseIdentity } from '../../scripts/release-cli-compatibility.mjs';

const repoRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));

function readManifest(relativePath) {
return JSON.parse(readFileSync(new URL(relativePath, import.meta.url), 'utf8'));
Expand Down Expand Up @@ -61,19 +66,21 @@ async function stageReleaseManifests({ packager }) {
}

const rootManifest = readManifest('../../package.json');
const { runtimeHostSetupPackage } = resolveProductManifestIdentity({
const { runtimeHostSetupPackage, version: productVersion } = resolveProductManifestIdentity({
rootManifest,
desktopManifest: readManifest('./package.json'),
cliManifest: readManifest('../../packages/cli/package.json'),
});

const baseDesktopBuilderConfig = {
appId: 'com.maka.desktop',
productName: 'Maka',
artifactName: 'Maka-${version}-mac-${arch}.${ext}',
asar: true,
beforePack: stageReleaseManifests,
extraMetadata: { runtimeHostSetupPackage, makaUpdateChannel: 'release' },
extraMetadata: {
runtimeHostSetupPackage,
makaUpdateChannel: 'release',
},
directories: {
output: 'release',
},
Expand Down Expand Up @@ -319,18 +326,49 @@ const baseDesktopBuilderConfig = {

export function resolveDesktopBuilderConfig(environment = process.env) {
const nightlyVersion = environment.MAKA_DESKTOP_NIGHTLY_VERSION?.trim();
if (!nightlyVersion) return baseDesktopBuilderConfig;
const version = resolveDesktopBuildVersion(rootManifest.version, environment);
const version = nightlyVersion
? resolveDesktopBuildVersion(rootManifest.version, environment)
: productVersion;
const makaReleaseIdentity = resolveMakaReleaseIdentity({
version,
sourceCommit: resolvePackagingSourceCommit(environment),
sourcePath: join(repoRoot, 'packages/runtime-host/src/protocol/index.ts'),
});
if (!nightlyVersion) {
return {
...baseDesktopBuilderConfig,
extraMetadata: {
...baseDesktopBuilderConfig.extraMetadata,
makaReleaseIdentity,
},
};
}
return {
...baseDesktopBuilderConfig,
extraMetadata: {
...baseDesktopBuilderConfig.extraMetadata,
version,
runtimeHostSetupPackage: resolveRuntimeHostSetupPackage(rootManifest.version, environment),
makaReleaseIdentity,
makaUpdateChannel: 'nightly',
},
publish: [{ provider: 'github', owner: 'apache', repo: 'maka', channel: 'dev' }],
};
}

function resolvePackagingSourceCommit(environment = process.env) {
const configured = environment.MAKA_RELEASE_SOURCE_COMMIT?.trim();
if (configured) return configured;
try {
return execFileSync('git', ['rev-parse', 'HEAD'], {
cwd: repoRoot,
encoding: 'utf8',
}).trim();
} catch (error) {
const githubSha = environment.GITHUB_SHA?.trim();
if (githubSha) return githubSha;
throw new Error('Desktop packaging requires an exact source commit SHA', { cause: error });
}
}

export default resolveDesktopBuilderConfig();
18 changes: 18 additions & 0 deletions docs/cli-npm-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ retaining Maka's stronger protected-Environment, staged-publishing, 2FA, and Fin
- Do not rebuild between validation, staging, approval, and finalization.
- Never reuse a public version. Formal product fixes require a new patch, minor, or major version.

## Runtime Host compatibility matrix

Desktop and npm CLI release artifacts carry the same `makaReleaseIdentity`: schema version,
product version, exact source commit, and Runtime Host compatibility epoch. Release validation
requires this identity to match across artifacts. The network handshake then requires an exact
epoch match before admitting any Domain command.

| Client artifact | Runtime Host artifact | Result |
| --- | --- | --- |
| Desktop/CLI from the same product source identity | Runtime Host from that same identity | Supported |
| Any client | Runtime Host with a different compatibility epoch | Rejected as `incompatible` during handshake |
| Desktop `0.1.11` (epoch 25) | `maka-agent@0.1.0-beta.1` (epoch 24) | Unsupported; upgrade to a newly published matching release |

The source commit and version are release metadata, not additional wire-handshake fields; equal
epochs alone do not make independently built artifacts a supported release pair. Because public
npm versions are immutable, do not overwrite `0.1.0-beta.1`; publish a new version from one exact
approved source commit and update the remote Host package.

The workflow boundaries are:

1. [npm publication](../.github/workflows/npm-publication.yml) is the only Trusted Publisher caller.
Expand Down
16 changes: 16 additions & 0 deletions docs/cli-npm-release.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ source RC 阶段的 [npm 预检](../.github/ASF_NPM_RELEASE.md) 是更早执行
- validation、staging、approval 和 finalization 之间不得重新构建;
- 已公开的版本不得复用。正式产品修复必须使用新的 patch、minor 或 major 版本。

## Runtime Host 兼容性矩阵

Desktop 与 npm CLI 发布物携带同一个 `makaReleaseIdentity`:schema version、产品版本、精确
source commit 以及 Runtime Host compatibility epoch。发布校验要求这些身份在各 artifact
之间一致;网络握手随后要求 epoch 精确相等,并在接纳任何 Domain command 前完成检查。

| Client artifact | Runtime Host artifact | 结果 |
| --- | --- | --- |
| 来自同一产品 source identity 的 Desktop/CLI | 来自同一 identity 的 Runtime Host | 支持 |
| 任意 Client | compatibility epoch 不同的 Runtime Host | 握手阶段返回 `incompatible` 并拒绝 |
| Desktop `0.1.11`(epoch 25) | `maka-agent@0.1.0-beta.1`(epoch 24) | 不支持;升级到新发布的匹配版本 |

source commit 和版本属于发布元数据,不是额外的 wire-handshake 字段;仅 epoch 相等并不使
独立构建的 artifact 自动成为受支持的版本组合。由于公开 npm 版本不可覆盖,不要改写
`0.1.0-beta.1`;应从一个精确批准的 source commit 发布新版本,并更新远程 Host 包。

workflow 边界分别是:

1. [npm publication](../.github/workflows/npm-publication.yml) 是唯一的 Trusted Publisher
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"check:runtime-host-peer-dependencies": "node scripts/generate-runtime-host-peer-dependencies.mjs --check",
"generate:runtime-host-peer-notices": "node scripts/generate-runtime-host-peer-notices.mjs",
"check:runtime-host-peer-notices": "node scripts/generate-runtime-host-peer-notices.mjs --check",
"check:release": "npm run check:stale && npm run check:third-party-notices && npm run check:cli-third-party-notices && npm run check:model-metadata && npm run check:product-release-identity && npm run check:asf-npm && node --test scripts/product-nightly.test.mjs scripts/desktop-release-targets.test.mjs scripts/verify-linux-harness.test.mjs scripts/desktop-nightly.test.mjs scripts/desktop-nightly-stage.test.mjs scripts/desktop-nightly-release.test.mjs scripts/desktop-nightly-workflow-policy.test.mjs scripts/product-release.test.mjs scripts/product-release-authority.test.mjs scripts/release-cli-file-policy.test.mjs scripts/release-cli-artifact-policy.test.mjs scripts/release-cli-eval-support.test.mjs scripts/release-cli-publication.test.mjs scripts/release-cli-runtime-host-diagnostics.test.mjs scripts/qualify-released-cli-state-root.test.mjs scripts/release-cli-workflow-policy.test.mjs scripts/verify-packaged-app.test.mjs scripts/third-party-closure.test.mjs scripts/generate-third-party-notices.test.mjs scripts/source-legal-inventory.test.mjs scripts/sync-model-metadata.test.mjs scripts/prepare-windows-upgrade-baseline.test.mjs scripts/windows-package-source-closure.test.mjs",
"check:release": "npm run check:stale && npm run check:third-party-notices && npm run check:cli-third-party-notices && npm run check:model-metadata && npm run check:product-release-identity && npm run check:asf-npm && node --test --test-concurrency=1 scripts/product-nightly.test.mjs scripts/desktop-release-targets.test.mjs scripts/verify-linux-harness.test.mjs scripts/desktop-nightly.test.mjs scripts/desktop-nightly-stage.test.mjs scripts/desktop-nightly-release.test.mjs scripts/desktop-nightly-workflow-policy.test.mjs scripts/product-release.test.mjs scripts/product-release-authority.test.mjs scripts/release-cli-compatibility.test.mjs scripts/release-cli-websocket-smoke.test.mjs scripts/release-cli-file-policy.test.mjs scripts/release-cli-artifact-policy.test.mjs scripts/release-cli-eval-support.test.mjs scripts/release-cli-publication.test.mjs scripts/release-cli-runtime-host-diagnostics.test.mjs scripts/qualify-released-cli-state-root.test.mjs scripts/release-cli-workflow-policy.test.mjs scripts/verify-packaged-app.test.mjs scripts/third-party-closure.test.mjs scripts/generate-third-party-notices.test.mjs scripts/source-legal-inventory.test.mjs scripts/sync-model-metadata.test.mjs scripts/prepare-windows-upgrade-baseline.test.mjs scripts/windows-package-source-closure.test.mjs",
"package:macos-arm64": "node scripts/package-macos.mjs arm64",
"package:macos-x64": "node scripts/package-macos.mjs x64",
"verify:macos": "node scripts/verify-macos-dmg.mjs",
Expand Down
13 changes: 13 additions & 0 deletions scripts/ci-workflow-policy.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,19 @@ test('installed-package validation discards superseded pull request runs', () =>
);
});

test('CLI package validation watches every release smoke helper', () => {
const filtered = new Set(readPullRequestPathFilter('cli-package-validation.yml'));
for (const path of [
'scripts/release-cli-compatibility.mjs',
'scripts/release-cli-compatibility.test.mjs',
'scripts/release-cli-websocket-smoke.mjs',
'scripts/release-cli-websocket-smoke.test.mjs',
'scripts/smoke-release-cli-package.mjs',
]) {
assert.ok(filtered.has(path), `${path} is not covered by the CLI validation path filter`);
}
});

test('the recovery lane keeps every run kind out of one shared concurrency group', () => {
const workflow = readWorkflow('windows-recovery.yml');

Expand Down
13 changes: 13 additions & 0 deletions scripts/desktop-nightly.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { createRequire } from 'node:module';
import { readFile } from 'node:fs/promises';
import { promisify } from 'node:util';
import { basename, dirname, join } from 'node:path';
import { test } from 'node:test';
Expand Down Expand Up @@ -262,3 +263,15 @@ test('packaging observes a valid nightly version without changing product manife
'maka-agent@0.2.0-dev.42.20260829',
);
});

test('version-bumped auto-update builds carry the matching release identity version', async () => {
const scripts = await Promise.all(
['package-windows-autoupdate-next.mjs', 'package-macos-autoupdate-next.mjs'].map((name) =>
readFile(new URL(`./${name}`, import.meta.url), 'utf8'),
),
);

for (const source of scripts) {
assert.match(source, /`-c\.extraMetadata\.makaReleaseIdentity\.version=\$\{nextVersion\}`/u);
}
});
22 changes: 20 additions & 2 deletions scripts/package-macos-arm64-cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'nod
import { fileURLToPath, pathToFileURL } from 'node:url';
import { promisify } from 'node:util';
import { resolveProductReleaseIdentity } from './product-release-identity.mjs';
import {
assertRuntimeHostCompatibilityEpoch,
resolveMakaReleaseIdentity,
} from './release-cli-compatibility.mjs';
import {
isMakaDevelopmentArtifact,
isThirdPartyDevelopmentArtifact,
Expand Down Expand Up @@ -862,15 +866,21 @@ export async function packageMacosArm64Cli({
readFile(join(repoRoot, 'apps', 'desktop', 'package.json'), 'utf8').then(JSON.parse),
readFile(join(repoRoot, 'packages', 'cli', 'package.json'), 'utf8').then(JSON.parse),
resolveCliWorkspacePackages(),
inspect('git', ['rev-parse', 'HEAD']),
env.MAKA_RELEASE_SOURCE_COMMIT?.trim() || inspect('git', ['rev-parse', 'HEAD']),
]);
const sourceCommit = sourceCommitResult.stdout.trim();
const sourceCommit =
typeof sourceCommitResult === 'string' ? sourceCommitResult : sourceCommitResult.stdout.trim();
const identity = resolveProductReleaseIdentity({
rootManifest,
desktopManifest,
cliManifest,
sha: sourceCommit,
});
const runtimeHostReleaseIdentity = resolveMakaReleaseIdentity({
version: identity.version,
sourceCommit,
sourcePath: join(repoRoot, 'packages/runtime-host/src/protocol/index.ts'),
});
if (releaseSigning) assertReleaseSigningEnvironment(env);
if (!nodeArchivePath) {
throw new Error(`Set MAKA_CLI_NODE_ARCHIVE to the verified ${identity.nodeArchive} path.`);
Expand Down Expand Up @@ -961,6 +971,13 @@ export async function packageMacosArm64Cli({
chmod(join(binDirectory, 'maka'), 0o755),
]);
await assertWorkspaceLinks(archiveRoot, workspacePackages);
assertRuntimeHostCompatibilityEpoch({
sourcePath: join(repoRoot, 'packages/runtime-host/src/protocol/index.ts'),
packagedPath: join(
archiveRoot,
'libexec/node_modules/@maka/runtime-host/dist/protocol/index.js',
),
});

const thirdPartyNoticesPath = join(archiveRoot, 'THIRD_PARTY_NOTICES.txt');
await copyFile(
Expand All @@ -976,6 +993,7 @@ export async function packageMacosArm64Cli({
product: 'Maka',
version,
sourceCommit,
makaReleaseIdentity: runtimeHostReleaseIdentity,
platform: 'macos',
architecture: 'arm64',
publicCommands: identity.publicCommands,
Expand Down
1 change: 1 addition & 0 deletions scripts/package-macos-autoupdate-next.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export async function packageMacosAutoupdateNext({
'--publish',
'never',
`-c.extraMetadata.version=${nextVersion}`,
`-c.extraMetadata.makaReleaseIdentity.version=${nextVersion}`,
'-c.extraMetadata.makaUpdateTestProfile=true',
'-c.mac.notarize=false',
'-c.directories.output=release-autoupdate-next',
Expand Down
1 change: 1 addition & 0 deletions scripts/package-windows-autoupdate-next.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export async function packageWindowsAutoupdateNext({
'--publish',
'never',
`-c.extraMetadata.version=${nextVersion}`,
`-c.extraMetadata.makaReleaseIdentity.version=${nextVersion}`,
'-c.directories.output=release-autoupdate-next',
]);

Expand Down
Loading
Loading