diff --git a/product-source/hololake-clean-desktop/package-lock.json b/product-source/hololake-clean-desktop/package-lock.json index fe2f8d663..230cfb121 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.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hololake-clean-desktop", - "version": "1.1.0", + "version": "1.2.0", "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 d3e4ed535..857dc3623 100644 --- a/product-source/hololake-clean-desktop/package.json +++ b/product-source/hololake-clean-desktop/package.json @@ -1,12 +1,13 @@ { "name": "hololake-clean-desktop", "private": true, - "version": "1.1.0", + "version": "1.2.0", "type": "module", "scripts": { "dev": "vite --host 127.0.0.1", "build": "tsc -b && vite build", - "test": "npm run build", + "test": "npm run build && node --test scripts/*.test.mjs", + "release:manifest": "node scripts/build-update-manifest.mjs", "tauri": "tauri" }, "dependencies": { diff --git a/product-source/hololake-clean-desktop/registries/module-registry.json b/product-source/hololake-clean-desktop/registries/module-registry.json index 073acfd9f..d5b57d66e 100644 --- a/product-source/hololake-clean-desktop/registries/module-registry.json +++ b/product-source/hololake-clean-desktop/registries/module-registry.json @@ -49,7 +49,7 @@ "kind": "SYSTEM_FOUNDATION", "audience": "SYSTEM", "summary_zh": "只接收通过光湖信任根验证的软件更新。", - "state": "CLIENT_TRUST_ROOT_BOUND_SERVER_HTTP_204_NO_PUBLIC_RELEASE", + "state": "CLIENT_TRUST_ROOT_BOUND_MANIFEST_BUILDER_VERIFIED_SERVER_HTTP_204_NO_PUBLIC_RELEASE", "source": "src-tauri/tauri.conf.json#plugins.updater" }, { diff --git a/product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs b/product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs new file mode 100644 index 000000000..af1ae023e --- /dev/null +++ b/product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +import crypto from 'node:crypto' +import fs from 'node:fs' +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +function required(value, name) { + if (!value) throw new Error(`${name}_REQUIRED`) + return value +} + +export function buildManifest({ version, artifact, signatureFile, baseUrl, notes, publishedAt }) { + if (!/^\d+\.\d+\.\d+$/.test(required(version, 'VERSION'))) throw new Error('VERSION_INVALID') + const artifactPath = path.resolve(required(artifact, 'ARTIFACT')) + const signaturePath = path.resolve(required(signatureFile, 'SIGNATURE')) + if (!fs.statSync(artifactPath, { throwIfNoEntry: false })?.isFile()) throw new Error('ARTIFACT_NOT_READABLE') + if (!fs.statSync(signaturePath, { throwIfNoEntry: false })?.isFile()) throw new Error('SIGNATURE_NOT_READABLE') + const signature = fs.readFileSync(signaturePath, 'utf8').trim() + if (signature.length < 32) throw new Error('SIGNATURE_INVALID') + const root = new URL(required(baseUrl, 'BASE_URL')) + if (root.protocol !== 'https:') throw new Error('HTTPS_BASE_URL_REQUIRED') + if (root.username || root.password || root.search || root.hash) throw new Error('BASE_URL_MUST_NOT_CONTAIN_CREDENTIALS_OR_QUERY') + const bytes = fs.readFileSync(artifactPath) + const fileName = path.basename(artifactPath) + const url = new URL(fileName, root.href.endsWith('/') ? root : new URL(`${root.href}/`)).href + return { + version, + notes: notes || 'HoloLake signed update', + pub_date: publishedAt || new Date().toISOString(), + platforms: { + 'darwin-aarch64': { + signature, + url, + sha256: crypto.createHash('sha256').update(bytes).digest('hex'), + bytes: bytes.length, + }, + }, + } +} + +function parseArgs(values) { + const result = {} + for (let index = 0; index < values.length; index += 2) { + if (!values[index]?.startsWith('--') || values[index + 1] === undefined) throw new Error('ARGUMENTS_INVALID') + result[values[index].slice(2)] = values[index + 1] + } + return result +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const args = parseArgs(process.argv.slice(2)) + const manifest = buildManifest({ + version: args.version, + artifact: args.artifact, + signatureFile: args.signature, + baseUrl: args['base-url'], + notes: args.notes, + publishedAt: args['published-at'], + }) + const output = path.resolve(required(args.output, 'OUTPUT')) + fs.mkdirSync(path.dirname(output), { recursive: true }) + const temporary = `${output}.tmp` + fs.writeFileSync(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o644 }) + fs.renameSync(temporary, output) + process.stdout.write(`${JSON.stringify({ outcome: 'MANIFEST_WRITTEN', output, version: manifest.version, platform: 'darwin-aarch64' })}\n`) +} + diff --git a/product-source/hololake-clean-desktop/scripts/build-update-manifest.test.mjs b/product-source/hololake-clean-desktop/scripts/build-update-manifest.test.mjs new file mode 100644 index 000000000..f83b9b538 --- /dev/null +++ b/product-source/hololake-clean-desktop/scripts/build-update-manifest.test.mjs @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' +import { buildManifest } from './build-update-manifest.mjs' + +function fixture() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-update-manifest-')) + const artifact = path.join(root, 'HoloLake.app.tar.gz') + const signatureFile = `${artifact}.sig` + fs.writeFileSync(artifact, 'signed-update-bytes') + fs.writeFileSync(signatureFile, 'trusted-signature-material-that-is-long-enough') + return { artifact, signatureFile } +} + +test('builds an HTTPS arm64 manifest with immutable evidence', () => { + const files = fixture() + const manifest = buildManifest({ + version: '1.2.0', ...files, + baseUrl: 'https://guanghulab.com/hololake/releases/1.2.0/', + notes: 'Current direct-language baseline', + publishedAt: '2026-09-03T00:00:00Z', + }) + assert.equal(manifest.version, '1.2.0') + assert.equal(manifest.platforms['darwin-aarch64'].bytes, 19) + assert.match(manifest.platforms['darwin-aarch64'].url, /^https:\/\/guanghulab\.com\//) + assert.equal(manifest.platforms['darwin-aarch64'].sha256.length, 64) +}) + +test('rejects HTTP, missing signatures and malformed versions', () => { + const files = fixture() + assert.throws(() => buildManifest({ version: '1.2', ...files, baseUrl: 'https://guanghulab.com/' }), /VERSION_INVALID/) + assert.throws(() => buildManifest({ version: '1.2.0', ...files, baseUrl: 'http://guanghulab.com/' }), /HTTPS_BASE_URL_REQUIRED/) + fs.writeFileSync(files.signatureFile, 'short') + assert.throws(() => buildManifest({ version: '1.2.0', ...files, baseUrl: 'https://guanghulab.com/' }), /SIGNATURE_INVALID/) +}) diff --git a/product-source/hololake-clean-desktop/src-tauri/Cargo.lock b/product-source/hololake-clean-desktop/src-tauri/Cargo.lock index a98630e11..09b055ba7 100644 --- a/product-source/hololake-clean-desktop/src-tauri/Cargo.lock +++ b/product-source/hololake-clean-desktop/src-tauri/Cargo.lock @@ -1328,7 +1328,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hololake-clean-desktop" -version = "1.1.0" +version = "1.2.0" 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 3d263b065..b25c8eb32 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.1.0" +version = "1.2.0" 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 b9db1f7e5..7e92bf3b6 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.1.0", + "version": "1.2.0", "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 94bae0727..d1c15abfe 100644 --- a/routing/hololake-current-architecture.json +++ b/routing/hololake-current-architecture.json @@ -14,13 +14,15 @@ "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.1.0", + "public_runtime_version": "1.2.0_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", "typed_language_sources": ["USER_MESSAGE", "PERSONA_RESPONSE", "EXTERNAL_AI_MESSAGE", "SYSTEM_CONTEXT", "PROTOCOL_EVENT", "AGENT_ACTION", "TOOL_RESULT", "SYSTEM_RECEIPT"], "public_update_endpoint": "https://guanghulab.com/hololake/releases/latest.json", "public_update_release_state": "SERVER_HTTP_204_NO_PUBLIC_RELEASE", + "public_update_manifest_builder": "product-source/hololake-clean-desktop/scripts/build-update-manifest.mjs", + "public_update_candidate_version": "1.2.0", "enterprise_repository_registry_state": "BLUE_ISOLATED_FIVE_CENTRAL_REPOSITORIES_AND_FIVE_HUMAN_WORK_REPOSITORIES_TEAM_PERMISSIONS_NOT_ASSIGNED_NO_PUBLIC_CUTOVER", "bingshuo_enterprise_language_master_work_repository": "REPO-016:bingshuo/guanghu-language-master-work", "team_enterprise_login": "https://guanghu.chat/code/user/login", @@ -195,6 +197,7 @@ "runtime_implemented": true, "runtime_state": "LOCAL_CLEAN_V1_1_0_INSTALLED_BASELINE_REOPENED_FOR_DIRECT_LANGUAGE_ACCEPTANCE", "local_application_version": "1.1.0", + "local_candidate_version": "1.2.0", "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", @@ -763,7 +766,7 @@ "local_source_commit": "e88805cf9123edab8237e8b87a9cec4a2bc4e798", "canonical_public_source_repository": "REPO-014", "canonical_public_source_path": "product-source/hololake-clean-desktop", - "source_artifact_alignment": "SOURCE_AT_REPO014_MAIN_INSTALLED_1_1_0_PRESENT_NEW_DIRECT_LANGUAGE_CHANGES_NOT_YET_BUILT_OR_INSTALLED", + "source_artifact_alignment": "LOCAL_1_2_0_SOURCE_CANDIDATE_UNPUBLISHED_INSTALLED_1_1_0_STILL_ACTIVE", "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",