From 7593f9d3f06b2f22206ad6a05665c35443f7426b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Thu, 3 Sep 2026 22:13:56 +0800 Subject: [PATCH] fix(release): require exact current source commit --- .../hololake-clean-desktop/package-lock.json | 4 +- .../hololake-clean-desktop/package.json | 3 +- .../build-controller-release-bundle.mjs | 130 ++++++++++++++++++ .../build-controller-release-bundle.test.mjs | 15 ++ .../src-tauri/Cargo.lock | 2 +- .../src-tauri/Cargo.toml | 2 +- .../src-tauri/tauri.conf.json | 2 +- routing/hololake-current-architecture.json | 6 +- 8 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.mjs create mode 100644 product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.test.mjs diff --git a/product-source/hololake-clean-desktop/package-lock.json b/product-source/hololake-clean-desktop/package-lock.json index dcf8b1bb8..13b64d41c 100644 --- a/product-source/hololake-clean-desktop/package-lock.json +++ b/product-source/hololake-clean-desktop/package-lock.json @@ -1,12 +1,12 @@ { "name": "hololake-clean-desktop", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hololake-clean-desktop", - "version": "1.2.1", + "version": "1.2.2", "dependencies": { "@tauri-apps/api": "2.10.1", "@tauri-apps/plugin-dialog": "2.7.2", diff --git a/product-source/hololake-clean-desktop/package.json b/product-source/hololake-clean-desktop/package.json index c97739fad..cb564f0e8 100644 --- a/product-source/hololake-clean-desktop/package.json +++ b/product-source/hololake-clean-desktop/package.json @@ -1,13 +1,14 @@ { "name": "hololake-clean-desktop", "private": true, - "version": "1.2.1", + "version": "1.2.2", "type": "module", "scripts": { "dev": "vite --host 127.0.0.1", "build": "tsc -b && vite build", "test": "npm run build && node --test scripts/*.test.mjs", "release:manifest": "node scripts/build-update-manifest.mjs", + "release:controller-bundle": "node scripts/build-controller-release-bundle.mjs", "tauri": "tauri" }, "dependencies": { diff --git a/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.mjs b/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.mjs new file mode 100644 index 000000000..eb9a06b58 --- /dev/null +++ b/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +import crypto from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import { pathToFileURL } from 'node:url' +import { execFileSync } from 'node:child_process' + +const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex') +const required = (value, name) => { + if (!value) throw new Error(`${name}_REQUIRED`) + return value +} +const write = (file, value) => fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600, flag: 'wx' }) + +export function buildControllerBundle(options) { + const version = required(options.version, 'VERSION') + const sourceCommit = required(options.sourceCommit, 'SOURCE_COMMIT') + if (!/^\d+\.\d+\.\d+$/.test(version)) throw new Error('VERSION_INVALID') + if (!/^[a-f0-9]{40}$/.test(sourceCommit)) throw new Error('SOURCE_COMMIT_INVALID') + const repository = path.resolve(options.repository || path.join(import.meta.dirname, '../../..')) + const head = execFileSync('git', ['-C', repository, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() + if (head !== sourceCommit) throw new Error(`SOURCE_COMMIT_NOT_CURRENT_HEAD:${head}`) + execFileSync('git', ['-C', repository, 'cat-file', '-e', `${sourceCommit}^{commit}`]) + const output = path.resolve(required(options.output, 'OUTPUT')) + if (fs.existsSync(output)) throw new Error('OUTPUT_ALREADY_EXISTS') + const artifact = path.resolve(required(options.artifact, 'ARTIFACT')) + const signatureFile = path.resolve(required(options.signatureFile, 'SIGNATURE')) + const bytes = fs.readFileSync(artifact) + const signature = fs.readFileSync(signatureFile, 'utf8').trim() + if (signature.length < 32) throw new Error('SIGNATURE_INVALID') + const base = new URL(required(options.baseUrl, 'BASE_URL')) + if (base.protocol !== 'https:' || base.username || base.password || base.search || base.hash) throw new Error('BASE_URL_INVALID') + const artifactName = path.basename(artifact) + const releaseId = `GH-HOLOLAKE-RELEASE-${version}` + const codesignId = `GH-HOLOLAKE-CODESIGN-${version}` + const notarizationId = `GH-HOLOLAKE-NOTARIZATION-${version}` + const approvalId = `GH-HOLOLAKE-APPROVAL-${version}-ICE-GL-INFINITY` + const latest = { + schema: 'hololake.release-broadcast/v1', + releaseId, + version, + notes: options.notes || 'HoloLake signed notarized release', + pub_date: required(options.publishedAt, 'PUBLISHED_AT'), + hololake: { + restart: { required: true, automaticAllowed: false }, + rollback: { supported: true, healthReceiptRequired: true }, + }, + platforms: { + 'darwin-aarch64': { + signature, + url: new URL(artifactName, base.href.endsWith('/') ? base : new URL(`${base.href}/`)).href, + size: bytes.length, + sha256: sha256(bytes), + platformCodeSignatureReceipt: codesignId, + notarizationReceipt: notarizationId, + }, + }, + } + const latestBytes = Buffer.from(`${JSON.stringify(latest, null, 2)}\n`) + const broadcastSha256 = sha256(latestBytes) + fs.mkdirSync(output, { recursive: false, mode: 0o700 }) + fs.copyFileSync(artifact, path.join(output, artifactName), fs.constants.COPYFILE_EXCL) + fs.chmodSync(path.join(output, artifactName), 0o600) + fs.writeFileSync(path.join(output, 'latest.json'), latestBytes, { mode: 0o600, flag: 'wx' }) + write(path.join(output, `${codesignId}.json`), { + schema: 'hololake.platform-code-signature-receipt/v1', + state: 'DEVELOPER_ID_SIGNATURE_STRICT_AND_GATEKEEPER_ACCEPTED', + sourceCommit, + version, + artifactSha256: sha256(bytes), + signingIdentity: 'Developer ID Application: bei sun (825A9L3G7Q)', + }) + write(path.join(output, `${notarizationId}.json`), { + schema: 'hololake.apple-notarization-receipt/v1', + state: 'APPLE_NOTARIZATION_ACCEPTED_AND_STAPLED', + sourceCommit, + version, + appSubmissionId: required(options.appSubmissionId, 'APP_SUBMISSION_ID'), + dmgSubmissionId: required(options.dmgSubmissionId, 'DMG_SUBMISSION_ID'), + staplerValidation: 'PASS', + gatekeeper: 'NOTARIZED_DEVELOPER_ID_ACCEPTED', + }) + write(path.join(output, 'pipeline-receipt.json'), { + schema: 'hololake.signed-release-pipeline-receipt/v1', + state: 'SIGNED_NOTARIZED_RELEASE_BROADCAST_READY_FOR_JD_CONTROLLER_UPLOAD', + sourceCommit, + version, + broadcastSha256, + automaticUpload: false, + automaticActivation: false, + }) + write(path.join(output, 'human-approval.json'), { + schema: 'hololake.release-broadcast-human-approval/v1', + state: 'HUMAN_APPROVED_EXACT_SIGNED_NOTARIZED_RELEASE', + approvalId, + releaseId, + version, + broadcastSha256, + humanAnchor: 'ICE-GL∞', + sourceMessageId: required(options.approvalMessageId, 'APPROVAL_MESSAGE_ID'), + sourceMessageSha256: required(options.approvalMessageSha256, 'APPROVAL_MESSAGE_SHA256'), + }) + return { output, releaseId, version, sourceCommit, broadcastSha256, approvalId } +} + +function args(values) { + const result = {} + for (let index = 0; index < values.length; index += 2) result[values[index].slice(2)] = values[index + 1] + return result +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const value = args(process.argv.slice(2)) + const result = buildControllerBundle({ + version: value.version, + sourceCommit: value['source-commit'], + artifact: value.artifact, + signatureFile: value.signature, + baseUrl: value['base-url'], + notes: value.notes, + publishedAt: value['published-at'], + appSubmissionId: value['app-submission-id'], + dmgSubmissionId: value['dmg-submission-id'], + approvalMessageId: value['approval-message-id'], + approvalMessageSha256: value['approval-message-sha256'], + output: value.output, + repository: value.repository, + }) + process.stdout.write(`${JSON.stringify({ outcome: 'CONTROLLER_BUNDLE_WRITTEN', ...result })}\n`) +} diff --git a/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.test.mjs b/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.test.mjs new file mode 100644 index 000000000..b62687f5a --- /dev/null +++ b/product-source/hololake-clean-desktop/scripts/build-controller-release-bundle.test.mjs @@ -0,0 +1,15 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { buildControllerBundle } from './build-controller-release-bundle.mjs' + +test('rejects a formatted but nonexistent or non-current source commit', () => { + assert.throws( + () => buildControllerBundle({ + version: '1.2.2', + sourceCommit: '0000000000000000000000000000000000000000', + repository: '../..', + output: '/tmp/hololake-controller-bundle-must-not-exist', + }), + /SOURCE_COMMIT_NOT_CURRENT_HEAD/, + ) +}) diff --git a/product-source/hololake-clean-desktop/src-tauri/Cargo.lock b/product-source/hololake-clean-desktop/src-tauri/Cargo.lock index 8c1fdbfbf..3a195275f 100644 --- a/product-source/hololake-clean-desktop/src-tauri/Cargo.lock +++ b/product-source/hololake-clean-desktop/src-tauri/Cargo.lock @@ -1400,7 +1400,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hololake-clean-desktop" -version = "1.2.1" +version = "1.2.2" dependencies = [ "base64 0.22.1", "chrono", diff --git a/product-source/hololake-clean-desktop/src-tauri/Cargo.toml b/product-source/hololake-clean-desktop/src-tauri/Cargo.toml index ad4ab2ccb..a0b84ed7a 100644 --- a/product-source/hololake-clean-desktop/src-tauri/Cargo.toml +++ b/product-source/hololake-clean-desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hololake-clean-desktop" -version = "1.2.1" +version = "1.2.2" description = "HoloLake clean personal language operating system shell" authors = ["HoloLake"] license = "AGPL-3.0-or-later" diff --git a/product-source/hololake-clean-desktop/src-tauri/tauri.conf.json b/product-source/hololake-clean-desktop/src-tauri/tauri.conf.json index d2b17fa24..7fd1a3888 100644 --- a/product-source/hololake-clean-desktop/src-tauri/tauri.conf.json +++ b/product-source/hololake-clean-desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "HoloLake", - "version": "1.2.1", + "version": "1.2.2", "identifier": "world.guanghu.hololake", "build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" }, "app": { diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json index 69efcd2d6..af8419e0a 100644 --- a/routing/hololake-current-architecture.json +++ b/routing/hololake-current-architecture.json @@ -14,7 +14,7 @@ "first_preinstalled_module": "HLP-MOD-KB-0001", "knowledge_module_state": "LOCAL_COMPLETE_HUMAN_VISUAL_RENDERING_WITH_CLASSIFICATION_COLORS_READING_TIME_SCROLL_OUTLINE_CREATE_READ_EDIT_IMPORT_EXPORT_TRASH_PRIVATE_GIT", "knowledge_human_rendering_source": "product-source/hololake-clean-desktop/src/modules/knowledge-render/index.tsx", - "public_runtime_version": "1.2.1_SOURCE_CANDIDATE", + "public_runtime_version": "1.2.2_SOURCE_CANDIDATE", "public_tcs_gir_agent": "LOCAL_ACTIVE_REGISTERED_KNOWLEDGE_CAPABILITIES_HUMAN_APPROVAL_REQUIRED", "public_persona_runtime": "LOCAL_TRIAL_ACTIVE_EXISTING_PERSONA_VERIFICATION_NOT_CLAIMED", "external_ai_realtime": "GLP_LOCAL_REALTIME_1_LOOPBACK_ACTIVE", @@ -199,7 +199,7 @@ "runtime_implemented": true, "runtime_state": "LOCAL_CLEAN_V1_1_2_0_INSTALLED_TCS_GIR_AGENT_TYPED_CONTEXT_MODULE_MARKET_ENTERPRISE_DEVICE_PROOF_AND_EXTERNAL_AI_REALTIME_VERIFIED", "local_application_version": "1.2.0", - "local_candidate_version": "1.2.1", + "local_candidate_version": "1.2.2", "local_implementation_commit": "92cce2797381f84762a989ce57b991da76412119", "official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION", "external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903", @@ -768,7 +768,7 @@ "local_source_commit": "d05a118f64858b081fc33e430f07704598a0a314", "canonical_public_source_repository": "REPO-014", "canonical_public_source_path": "product-source/hololake-clean-desktop", - "source_artifact_alignment": "LOCAL_1_2_1_ENTERPRISE_GATE_CLIENT_SOURCE_CANDIDATE_INSTALLED_1_2_0_STILL_ACTIVE", + "source_artifact_alignment": "LOCAL_1_2_2_SOURCE_CANDIDATE_INSTALLED_1_2_1_ACTIVE_PUBLIC_1_2_1_RETRACTED_FOR_SOURCE_COMMIT_MISMATCH", "previous_repo014_product_source_assessment": "HLP-DESKTOP-GAP-20260809-001_RETAINED_AS_HISTORY_NOT_CURRENT_ARTIFACT", "final_plan_complete": false, "model_receipt_donor_audit": "COMPLETE_DEFERRED_FROM_STAGE1",