fix(release): require exact current source commit
This commit is contained in:
parent
4e434cffdc
commit
7593f9d3f0
8 changed files with 155 additions and 9 deletions
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "hololake-clean-desktop",
|
"name": "hololake-clean-desktop",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hololake-clean-desktop",
|
"name": "hololake-clean-desktop",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.10.1",
|
"@tauri-apps/api": "2.10.1",
|
||||||
"@tauri-apps/plugin-dialog": "2.7.2",
|
"@tauri-apps/plugin-dialog": "2.7.2",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "hololake-clean-desktop",
|
"name": "hololake-clean-desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 127.0.0.1",
|
"dev": "vite --host 127.0.0.1",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"test": "npm run build && node --test scripts/*.test.mjs",
|
"test": "npm run build && node --test scripts/*.test.mjs",
|
||||||
"release:manifest": "node scripts/build-update-manifest.mjs",
|
"release:manifest": "node scripts/build-update-manifest.mjs",
|
||||||
|
"release:controller-bundle": "node scripts/build-controller-release-bundle.mjs",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -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`)
|
||||||
|
}
|
||||||
|
|
@ -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/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
@ -1400,7 +1400,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hololake-clean-desktop"
|
name = "hololake-clean-desktop"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hololake-clean-desktop"
|
name = "hololake-clean-desktop"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
description = "HoloLake clean personal language operating system shell"
|
description = "HoloLake clean personal language operating system shell"
|
||||||
authors = ["HoloLake"]
|
authors = ["HoloLake"]
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "HoloLake",
|
"productName": "HoloLake",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"identifier": "world.guanghu.hololake",
|
"identifier": "world.guanghu.hololake",
|
||||||
"build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" },
|
"build": { "frontendDist": "../dist", "devUrl": "http://127.0.0.1:5211", "beforeDevCommand": "npm run dev", "beforeBuildCommand": "npm run build" },
|
||||||
"app": {
|
"app": {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
"first_preinstalled_module": "HLP-MOD-KB-0001",
|
"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_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",
|
"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_tcs_gir_agent": "LOCAL_ACTIVE_REGISTERED_KNOWLEDGE_CAPABILITIES_HUMAN_APPROVAL_REQUIRED",
|
||||||
"public_persona_runtime": "LOCAL_TRIAL_ACTIVE_EXISTING_PERSONA_VERIFICATION_NOT_CLAIMED",
|
"public_persona_runtime": "LOCAL_TRIAL_ACTIVE_EXISTING_PERSONA_VERIFICATION_NOT_CLAIMED",
|
||||||
"external_ai_realtime": "GLP_LOCAL_REALTIME_1_LOOPBACK_ACTIVE",
|
"external_ai_realtime": "GLP_LOCAL_REALTIME_1_LOOPBACK_ACTIVE",
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
"runtime_implemented": true,
|
"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",
|
"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_application_version": "1.2.0",
|
||||||
"local_candidate_version": "1.2.1",
|
"local_candidate_version": "1.2.2",
|
||||||
"local_implementation_commit": "92cce2797381f84762a989ce57b991da76412119",
|
"local_implementation_commit": "92cce2797381f84762a989ce57b991da76412119",
|
||||||
"official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION",
|
"official_development_lane": "CURRENT_TASK_SCOPED_ZC001_EXECUTION",
|
||||||
"external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903",
|
"external_development_anchor_id": "TCS-EVENT-HOLOLAKE-CLEAN-V1-ZC001-REALITY-DEVELOPMENT-TAKEOVER-20260903",
|
||||||
|
|
@ -768,7 +768,7 @@
|
||||||
"local_source_commit": "d05a118f64858b081fc33e430f07704598a0a314",
|
"local_source_commit": "d05a118f64858b081fc33e430f07704598a0a314",
|
||||||
"canonical_public_source_repository": "REPO-014",
|
"canonical_public_source_repository": "REPO-014",
|
||||||
"canonical_public_source_path": "product-source/hololake-clean-desktop",
|
"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",
|
"previous_repo014_product_source_assessment": "HLP-DESKTOP-GAP-20260809-001_RETAINED_AS_HISTORY_NOT_CURRENT_ARTIFACT",
|
||||||
"final_plan_complete": false,
|
"final_plan_complete": false,
|
||||||
"model_receipt_donor_audit": "COMPLETE_DEFERRED_FROM_STAGE1",
|
"model_receipt_donor_audit": "COMPLETE_DEFERRED_FROM_STAGE1",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue