build(hololake): bind mac candidates to exact source
This commit is contained in:
parent
8f18c3fba3
commit
9299fc9442
6 changed files with 278 additions and 2 deletions
1
product-source/hololake-platform/.gitignore
vendored
1
product-source/hololake-platform/.gitignore
vendored
|
|
@ -46,6 +46,7 @@ src-tauri/target
|
||||||
|
|
||||||
# Generated mcp-server bundle (built by scripts/bundle-mcp-server.mjs)
|
# Generated mcp-server bundle (built by scripts/bundle-mcp-server.mjs)
|
||||||
src-tauri/resources/mcp-server/
|
src-tauri/resources/mcp-server/
|
||||||
|
src-tauri/resources/public-architecture/build-provenance.json
|
||||||
|
|
||||||
# Python cache
|
# Python cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ pnpm package:internal:windows
|
||||||
|
|
||||||
## 构建前检查
|
## 构建前检查
|
||||||
|
|
||||||
1. 工作树干净,当前分支包含目标 UI 和资源。
|
1. 工作树干净,当前分支包含目标 UI 和资源,并显式传入当前 `HOLOLAKE_DEVELOPMENT_ID`。
|
||||||
2. `tauri.conf.json`、`Cargo.toml`、`package.json` 版本一致。
|
2. `tauri.conf.json`、`Cargo.toml`、`package.json` 版本一致。
|
||||||
3. 运行相关测试、lint、TypeScript/Vite 构建。
|
3. 运行相关测试、lint、TypeScript/Vite 构建。
|
||||||
4. 确认公共研发入口和 GLS 文件已经进入源码:
|
4. 确认公共研发入口和 GLS 文件已经进入源码:
|
||||||
|
|
@ -45,6 +45,13 @@ pnpm package:internal:windows
|
||||||
- Windows 载荷必须为 `PE32+ x86-64`,NSIS 安装包必须存在;
|
- Windows 载荷必须为 `PE32+ x86-64`,NSIS 安装包必须存在;
|
||||||
- 每个安装包必须生成同名 `.sha256`,回传后再次校验。
|
- 每个安装包必须生成同名 `.sha256`,回传后再次校验。
|
||||||
|
|
||||||
|
`scripts/internal-release-provenance.mjs` 是来源硬门:
|
||||||
|
|
||||||
|
- 构建前只接受干净工作树,并把 REPO-014 完整提交、根树、`main` 分支、当前架构编号与版本、应用版本、开发编号、分发类型和目标架构写入包内 `build-provenance.json`;
|
||||||
|
- 打包后必须从应用包中精确找到且只找到一份来源记录,并逐字段回读一致;
|
||||||
|
- 来源记录在签名前进入应用资源,签名后补写任何文件都会破坏封印,不能作为发布流程;
|
||||||
|
- 来源记录只证明制品从哪份源码构建,不证明安装、部署、运行健康或人格主控权限。
|
||||||
|
|
||||||
内测 Mac 包是 ad-hoc 签名、未公证;Windows 包在没有商业证书时未做 Authenticode 签名。交付时必须明确说明,不能描述成正式签名发行版。
|
内测 Mac 包是 ad-hoc 签名、未公证;Windows 包在没有商业证书时未做 Authenticode 签名。交付时必须明确说明,不能描述成正式签名发行版。
|
||||||
|
|
||||||
## Windows 构建恢复顺序
|
## Windows 构建恢复顺序
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
"playwright:integration": "playwright test --config playwright.integration.config.ts",
|
"playwright:integration": "playwright test --config playwright.integration.config.ts",
|
||||||
"test:coverage": "node scripts/run-vitest-coverage.mjs",
|
"test:coverage": "node scripts/run-vitest-coverage.mjs",
|
||||||
"verify:internal-package": "node scripts/verify-internal-package-content.mjs",
|
"verify:internal-package": "node scripts/verify-internal-package-content.mjs",
|
||||||
"test:internal-release": "node --test scripts/internal-release-version.test.mjs",
|
"test:internal-release": "node --test scripts/internal-release-version.test.mjs scripts/internal-release-provenance.test.mjs",
|
||||||
"guard:deployment-source": "node scripts/deployment-source-guard.mjs",
|
"guard:deployment-source": "node scripts/deployment-source-guard.mjs",
|
||||||
"test:deployment-source": "node --test scripts/deployment-source-guard.test.mjs",
|
"test:deployment-source": "node --test scripts/deployment-source-guard.test.mjs",
|
||||||
"test:native-authority": "bash scripts/test-guanghu-native-authority.sh && node scripts/validate-guanghu-native-profile.mjs",
|
"test:native-authority": "bash scripts/test-guanghu-native-authority.sh && node scripts/validate-guanghu-native-profile.mjs",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ cd "$repo_root"
|
||||||
[[ "$(uname -s)" == "Darwin" ]] || { echo "macOS internal packages must be built on macOS." >&2; exit 2; }
|
[[ "$(uname -s)" == "Darwin" ]] || { echo "macOS internal packages must be built on macOS." >&2; exit 2; }
|
||||||
|
|
||||||
target="${HOLOLAKE_MAC_TARGET:-aarch64-apple-darwin}"
|
target="${HOLOLAKE_MAC_TARGET:-aarch64-apple-darwin}"
|
||||||
|
development_id="${HOLOLAKE_DEVELOPMENT_ID:?HOLOLAKE_DEVELOPMENT_ID is required for source-bound packaging}"
|
||||||
|
distribution="${HOLOLAKE_DISTRIBUTION:-internal}"
|
||||||
version="$(node scripts/internal-release-version.mjs src-tauri/tauri.conf.json "${HOLOLAKE_VERSION:-}")"
|
version="$(node scripts/internal-release-version.mjs src-tauri/tauri.conf.json "${HOLOLAKE_VERSION:-}")"
|
||||||
product_name="$(node -e "const fs=require('node:fs'); const value=JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8')).productName; if(!value) process.exit(1); process.stdout.write(value)")"
|
product_name="$(node -e "const fs=require('node:fs'); const value=JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json','utf8')).productName; if(!value) process.exit(1); process.stdout.write(value)")"
|
||||||
output_dir="${HOLOLAKE_OUTPUT_DIR:-$repo_root/artifacts/internal/$version}"
|
output_dir="${HOLOLAKE_OUTPUT_DIR:-$repo_root/artifacts/internal/$version}"
|
||||||
|
|
@ -14,6 +16,7 @@ target_root="${CARGO_TARGET_DIR:-$repo_root/src-tauri/target}"
|
||||||
bundle_dir="$target_root/$target/release/bundle/macos"
|
bundle_dir="$target_root/$target/release/bundle/macos"
|
||||||
app="$bundle_dir/${HOLOLAKE_APP_NAME:-$product_name}.app"
|
app="$bundle_dir/${HOLOLAKE_APP_NAME:-$product_name}.app"
|
||||||
installer="$output_dir/HoloLake-Era-$version-Mac-internal-aarch64.dmg"
|
installer="$output_dir/HoloLake-Era-$version-Mac-internal-aarch64.dmg"
|
||||||
|
provenance_source="$repo_root/src-tauri/resources/public-architecture/build-provenance.json"
|
||||||
tauri_config="${HOLOLAKE_TAURI_CONFIG:-}"
|
tauri_config="${HOLOLAKE_TAURI_CONFIG:-}"
|
||||||
if [[ -n "${HOLOLAKE_INSTALLER_BASENAME:-}" ]]; then
|
if [[ -n "${HOLOLAKE_INSTALLER_BASENAME:-}" ]]; then
|
||||||
installer_basename="${HOLOLAKE_INSTALLER_BASENAME//\{version\}/$version}"
|
installer_basename="${HOLOLAKE_INSTALLER_BASENAME//\{version\}/$version}"
|
||||||
|
|
@ -30,6 +33,15 @@ else
|
||||||
fi
|
fi
|
||||||
command -v hdiutil >/dev/null || { echo "hdiutil is required." >&2; exit 2; }
|
command -v hdiutil >/dev/null || { echo "hdiutil is required." >&2; exit 2; }
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
|
[[ ! -e "$provenance_source" ]] || { echo "Stale generated build provenance exists: $provenance_source" >&2; exit 1; }
|
||||||
|
cleanup_provenance() { rm -f "$provenance_source"; }
|
||||||
|
trap cleanup_provenance EXIT
|
||||||
|
|
||||||
|
node scripts/internal-release-provenance.mjs generate \
|
||||||
|
--output "$provenance_source" \
|
||||||
|
--development-id "$development_id" \
|
||||||
|
--distribution "$distribution" \
|
||||||
|
--target-triple "$target"
|
||||||
|
|
||||||
if [[ "${HOLOLAKE_SKIP_INSTALL:-0}" != "1" ]]; then
|
if [[ "${HOLOLAKE_SKIP_INSTALL:-0}" != "1" ]]; then
|
||||||
"${pnpm_cmd[@]}" install --frozen-lockfile
|
"${pnpm_cmd[@]}" install --frozen-lockfile
|
||||||
|
|
@ -41,6 +53,11 @@ if [[ -n "$tauri_config" ]]; then tauri_args+=(--config "$tauri_config"); fi
|
||||||
[[ -d "$app" ]] || { echo "No macOS application bundle was produced: $app" >&2; exit 1; }
|
[[ -d "$app" ]] || { echo "No macOS application bundle was produced: $app" >&2; exit 1; }
|
||||||
|
|
||||||
node scripts/verify-internal-package-content.mjs "$app"
|
node scripts/verify-internal-package-content.mjs "$app"
|
||||||
|
node scripts/internal-release-provenance.mjs verify \
|
||||||
|
--bundle "$app" \
|
||||||
|
--development-id "$development_id" \
|
||||||
|
--distribution "$distribution" \
|
||||||
|
--target-triple "$target"
|
||||||
codesign --force --deep --sign - "$app"
|
codesign --force --deep --sign - "$app"
|
||||||
codesign --verify --deep --strict "$app"
|
codesign --verify --deep --strict "$app"
|
||||||
hdiutil create -volname "HoloLake Era $version Internal" -srcfolder "$app" -ov -format UDZO "$installer"
|
hdiutil create -volname "HoloLake Era $version Internal" -srcfolder "$app" -ov -format UDZO "$installer"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { execFileSync } from 'node:child_process'
|
||||||
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
||||||
|
import { dirname, join, resolve } from 'node:path'
|
||||||
|
import { pathToFileURL } from 'node:url'
|
||||||
|
|
||||||
|
const COMMIT_PATTERN = /^[0-9a-f]{40}$/u
|
||||||
|
const VERSION_PATTERN = /^\d+\.\d+\.\d+$/u
|
||||||
|
const ARCHITECTURE_VERSION_PATTERN = /^\d{4}-\d{2}-\d{2}\.\d+$/u
|
||||||
|
const DEVELOPMENT_ID_PATTERN = /^DEV-\d{8}-\d{3}$/u
|
||||||
|
const CANONICAL_REPOSITORY = 'repo://guanghulab.com/code/bingshuo/hololake-system-architecture'
|
||||||
|
const CANONICAL_REPOSITORY_ID = 'REPO-014'
|
||||||
|
const CANONICAL_SOURCE_PATH = 'product-source/hololake-platform'
|
||||||
|
const PROVENANCE_FILE_NAME = 'build-provenance.json'
|
||||||
|
|
||||||
|
function requireExactString(value, label, pattern) {
|
||||||
|
if (typeof value !== 'string' || !pattern.test(value)) {
|
||||||
|
throw new Error(`invalid ${label}`)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildInternalReleaseProvenance(input) {
|
||||||
|
if (input.worktreeClean !== true) {
|
||||||
|
throw new Error('internal release provenance requires a clean source worktree')
|
||||||
|
}
|
||||||
|
if (input.sourceRepository !== CANONICAL_REPOSITORY) {
|
||||||
|
throw new Error('invalid source repository')
|
||||||
|
}
|
||||||
|
if (input.sourceRepositoryId !== CANONICAL_REPOSITORY_ID) {
|
||||||
|
throw new Error('invalid source repository id')
|
||||||
|
}
|
||||||
|
if (input.sourcePath !== CANONICAL_SOURCE_PATH) {
|
||||||
|
throw new Error('invalid source path')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema: 'hololake.internal-release-provenance/v1',
|
||||||
|
architecture: {
|
||||||
|
id: requireExactString(input.architectureId, 'architecture id', /^HLP-[A-Z0-9-]+$/u),
|
||||||
|
version: requireExactString(input.architectureVersion, 'architecture version', ARCHITECTURE_VERSION_PATTERN),
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
application_version: requireExactString(input.applicationVersion, 'application version', VERSION_PATTERN),
|
||||||
|
development_id: requireExactString(input.developmentId, 'development id', DEVELOPMENT_ID_PATTERN),
|
||||||
|
distribution: requireExactString(input.distribution, 'distribution', /^[a-z][a-z0-9-]+$/u),
|
||||||
|
recorded_at: requireExactString(input.recordedAt, 'recorded at', /^\d{4}-\d{2}-\d{2}T/u),
|
||||||
|
target_triple: requireExactString(input.targetTriple, 'target triple', /^[a-z0-9-]+$/u),
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
branch: requireExactString(input.sourceBranch, 'source branch', /^[A-Za-z0-9._/-]+$/u),
|
||||||
|
commit: requireExactString(input.sourceCommit, 'source commit', COMMIT_PATTERN),
|
||||||
|
path: input.sourcePath,
|
||||||
|
repository: input.sourceRepository,
|
||||||
|
repository_id: input.sourceRepositoryId,
|
||||||
|
tree: requireExactString(input.sourceTree, 'source tree', COMMIT_PATTERN),
|
||||||
|
worktree_clean: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectEqual(actual, expected, label) {
|
||||||
|
if (actual !== expected) throw new Error(`${label} mismatch`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateInternalReleaseProvenance(provenance, expected) {
|
||||||
|
if (!provenance || provenance.schema !== 'hololake.internal-release-provenance/v1') {
|
||||||
|
throw new Error('provenance schema mismatch')
|
||||||
|
}
|
||||||
|
expectEqual(provenance.source?.commit, expected.sourceCommit, 'source commit')
|
||||||
|
expectEqual(provenance.source?.tree, expected.sourceTree, 'source tree')
|
||||||
|
expectEqual(provenance.source?.branch, expected.sourceBranch, 'source branch')
|
||||||
|
expectEqual(provenance.source?.repository, expected.sourceRepository, 'source repository')
|
||||||
|
expectEqual(provenance.source?.repository_id, expected.sourceRepositoryId, 'source repository id')
|
||||||
|
expectEqual(provenance.source?.path, expected.sourcePath, 'source path')
|
||||||
|
expectEqual(provenance.source?.worktree_clean, expected.worktreeClean, 'source worktree state')
|
||||||
|
expectEqual(provenance.architecture?.id, expected.architectureId, 'architecture id')
|
||||||
|
expectEqual(provenance.architecture?.version, expected.architectureVersion, 'architecture version')
|
||||||
|
expectEqual(provenance.build?.application_version, expected.applicationVersion, 'application version')
|
||||||
|
expectEqual(provenance.build?.development_id, expected.developmentId, 'development id')
|
||||||
|
expectEqual(provenance.build?.distribution, expected.distribution, 'distribution')
|
||||||
|
expectEqual(provenance.build?.target_triple, expected.targetTriple, 'target triple')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function git(repositoryRoot, ...args) {
|
||||||
|
return execFileSync('git', ['-C', repositoryRoot, ...args], { encoding: 'utf8' }).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function repositoryExpectation({ repositoryRoot, developmentId, distribution, targetTriple }) {
|
||||||
|
const architecture = JSON.parse(await readFile(join(repositoryRoot, 'routing/hololake-current-architecture.json'), 'utf8'))
|
||||||
|
const application = JSON.parse(await readFile(join(repositoryRoot, CANONICAL_SOURCE_PATH, 'src-tauri/tauri.conf.json'), 'utf8'))
|
||||||
|
return {
|
||||||
|
sourceCommit: git(repositoryRoot, 'rev-parse', 'HEAD'),
|
||||||
|
sourceTree: git(repositoryRoot, 'rev-parse', 'HEAD^{tree}'),
|
||||||
|
sourceBranch: git(repositoryRoot, 'branch', '--show-current'),
|
||||||
|
sourceRepository: CANONICAL_REPOSITORY,
|
||||||
|
sourceRepositoryId: CANONICAL_REPOSITORY_ID,
|
||||||
|
sourcePath: CANONICAL_SOURCE_PATH,
|
||||||
|
architectureId: architecture.architecture_id,
|
||||||
|
architectureVersion: architecture.version,
|
||||||
|
applicationVersion: application.version,
|
||||||
|
developmentId,
|
||||||
|
distribution,
|
||||||
|
targetTriple,
|
||||||
|
worktreeClean: git(repositoryRoot, 'status', '--porcelain=v1', '--untracked-files=all') === '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeJsonAtomic(outputPath, value) {
|
||||||
|
await mkdir(dirname(outputPath), { recursive: true })
|
||||||
|
const temporary = `${outputPath}.${process.pid}.tmp`
|
||||||
|
await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' })
|
||||||
|
try {
|
||||||
|
await rename(temporary, outputPath)
|
||||||
|
} catch (error) {
|
||||||
|
await rm(temporary, { force: true })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findProvenanceFiles(directory) {
|
||||||
|
const found = []
|
||||||
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||||
|
const entryPath = join(directory, entry.name)
|
||||||
|
if (entry.isDirectory()) found.push(...await findProvenanceFiles(entryPath))
|
||||||
|
else if (entry.isFile() && entry.name === PROVENANCE_FILE_NAME) found.push(entryPath)
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
function option(args, name) {
|
||||||
|
const index = args.indexOf(name)
|
||||||
|
if (index === -1 || !args[index + 1]) throw new Error(`${name} is required`)
|
||||||
|
return args[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const [command, ...args] = process.argv.slice(2)
|
||||||
|
if (!['generate', 'verify'].includes(command)) {
|
||||||
|
throw new Error('Usage: internal-release-provenance.mjs <generate|verify> [options]')
|
||||||
|
}
|
||||||
|
|
||||||
|
const repositoryRoot = git(process.cwd(), 'rev-parse', '--show-toplevel')
|
||||||
|
const developmentId = option(args, '--development-id')
|
||||||
|
const distribution = option(args, '--distribution')
|
||||||
|
const targetTriple = option(args, '--target-triple')
|
||||||
|
const expected = await repositoryExpectation({ repositoryRoot, developmentId, distribution, targetTriple })
|
||||||
|
|
||||||
|
if (command === 'generate') {
|
||||||
|
const outputPath = resolve(option(args, '--output'))
|
||||||
|
const provenance = buildInternalReleaseProvenance({
|
||||||
|
...expected,
|
||||||
|
recordedAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
await writeJsonAtomic(outputPath, provenance)
|
||||||
|
process.stdout.write(`${outputPath}\n`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundlePath = resolve(option(args, '--bundle'))
|
||||||
|
const provenanceFiles = await findProvenanceFiles(bundlePath)
|
||||||
|
if (provenanceFiles.length !== 1) {
|
||||||
|
throw new Error(`expected exactly one packaged ${PROVENANCE_FILE_NAME}, found ${provenanceFiles.length}`)
|
||||||
|
}
|
||||||
|
const provenance = JSON.parse(await readFile(provenanceFiles[0], 'utf8'))
|
||||||
|
validateInternalReleaseProvenance(provenance, expected)
|
||||||
|
process.stdout.write(`${provenanceFiles[0]}\n`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
await main()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildInternalReleaseProvenance,
|
||||||
|
validateInternalReleaseProvenance,
|
||||||
|
} from './internal-release-provenance.mjs'
|
||||||
|
|
||||||
|
const sourceCommit = '1'.repeat(40)
|
||||||
|
const sourceTree = '2'.repeat(40)
|
||||||
|
|
||||||
|
function input(overrides = {}) {
|
||||||
|
return {
|
||||||
|
sourceCommit,
|
||||||
|
sourceTree,
|
||||||
|
sourceBranch: 'main',
|
||||||
|
sourceRepository: 'repo://guanghulab.com/code/bingshuo/hololake-system-architecture',
|
||||||
|
sourceRepositoryId: 'REPO-014',
|
||||||
|
sourcePath: 'product-source/hololake-platform',
|
||||||
|
architectureId: 'HLP-CURRENT-ARCH-001',
|
||||||
|
architectureVersion: '2026-08-12.16',
|
||||||
|
applicationVersion: '0.4.6',
|
||||||
|
developmentId: 'DEV-20260811-010',
|
||||||
|
distribution: 'local-candidate',
|
||||||
|
targetTriple: 'aarch64-apple-darwin',
|
||||||
|
recordedAt: '2026-08-12T07:00:00+08:00',
|
||||||
|
worktreeClean: true,
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('builds a source-bound local candidate provenance record', () => {
|
||||||
|
assert.deepEqual(buildInternalReleaseProvenance(input()), {
|
||||||
|
schema: 'hololake.internal-release-provenance/v1',
|
||||||
|
architecture: {
|
||||||
|
id: 'HLP-CURRENT-ARCH-001',
|
||||||
|
version: '2026-08-12.16',
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
application_version: '0.4.6',
|
||||||
|
development_id: 'DEV-20260811-010',
|
||||||
|
distribution: 'local-candidate',
|
||||||
|
recorded_at: '2026-08-12T07:00:00+08:00',
|
||||||
|
target_triple: 'aarch64-apple-darwin',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
branch: 'main',
|
||||||
|
commit: sourceCommit,
|
||||||
|
path: 'product-source/hololake-platform',
|
||||||
|
repository: 'repo://guanghulab.com/code/bingshuo/hololake-system-architecture',
|
||||||
|
repository_id: 'REPO-014',
|
||||||
|
tree: sourceTree,
|
||||||
|
worktree_clean: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('refuses to issue provenance for a dirty source tree', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => buildInternalReleaseProvenance(input({ worktreeClean: false })),
|
||||||
|
/clean source worktree/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('validates exact source, architecture, application and development bindings', () => {
|
||||||
|
const provenance = buildInternalReleaseProvenance(input())
|
||||||
|
|
||||||
|
assert.equal(validateInternalReleaseProvenance(provenance, input()), true)
|
||||||
|
assert.throws(
|
||||||
|
() => validateInternalReleaseProvenance(provenance, input({ sourceCommit: '3'.repeat(40) })),
|
||||||
|
/source commit mismatch/,
|
||||||
|
)
|
||||||
|
assert.throws(
|
||||||
|
() => validateInternalReleaseProvenance(provenance, input({ architectureVersion: '2026-08-12.17' })),
|
||||||
|
/architecture version mismatch/,
|
||||||
|
)
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue