From 3ff80d583a2b63835ef6d9cbc670f3b1041c580d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 11:15:45 +0800 Subject: [PATCH 1/7] fix(product): restore HoloLake world entry hierarchy --- .../hololake-platform/src/App.test.tsx | 12 ++--- product-source/hololake-platform/src/App.tsx | 12 +++-- .../src/components/FifthDomainSystems.tsx | 29 ++++++++---- .../src/components/GuanghuWorldLoginGate.tsx | 35 +++++++++----- .../src/components/HoloLakeHome.test.tsx | 47 ++++++++++++++++++- .../src/components/HoloLakeHome.tsx | 3 ++ 6 files changed, 105 insertions(+), 33 deletions(-) diff --git a/product-source/hololake-platform/src/App.test.tsx b/product-source/hololake-platform/src/App.test.tsx index f495a9ae2..7ff056eb9 100644 --- a/product-source/hololake-platform/src/App.test.tsx +++ b/product-source/hololake-platform/src/App.test.tsx @@ -762,7 +762,7 @@ describe('App', () => { }) }) - it('shows the external AI setup dialog from the menu when AI onboarding is active', async () => { + it('keeps external AI setup available from the menu without making it startup', async () => { localStorage.removeItem(AI_AGENTS_ONBOARDING_DISMISSED_STORAGE_NAME) localStorage.removeItem(CLAUDE_CODE_ONBOARDING_DISMISSED_STORAGE_NAME) mockCommandResults.get_ai_agents_status = { @@ -777,8 +777,9 @@ describe('App', () => { render() await waitFor(() => { - expect(screen.getByText('AI is ready')).toBeInTheDocument() + expect(screen.getByText('All Notes')).toBeInTheDocument() }, { timeout: SLOW_APP_READY_TIMEOUT_MS }) + expect(screen.queryByText('AI is ready')).not.toBeInTheDocument() await waitFor(() => { expect(typeof window.__laputaTest?.dispatchBrowserMenuCommand).toBe('function') @@ -792,7 +793,6 @@ describe('App', () => { expect(screen.getByText('Manage External AI Tools')).toBeInTheDocument() }) expect(screen.getByTestId('mcp-setup-dialog')).toBeInTheDocument() - expect(screen.queryByText('AI is ready')).not.toBeInTheDocument() }) it('routes right-panel AI chat messages to the selected default agent', async () => { @@ -903,7 +903,7 @@ describe('App', () => { expect(screen.getByTestId('welcome-open-folder')).toHaveTextContent('Open existing vault') }) - it('persists an existing vault and shows AI onboarding after first-run open', async () => { + it('persists an existing vault without putting optional AI setup in front of HoloLake', async () => { const selectedVaultPath = '/Users/mock/Documents/Work Vault' const saveVaultList = vi.fn() const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue('file:///Users/mock/Documents/Work%20Vault') @@ -947,9 +947,9 @@ describe('App', () => { expect(saveVaultList).toHaveBeenCalledTimes(1) await waitFor(() => { - expect(screen.getByTestId('ai-agents-onboarding-screen')).toBeInTheDocument() + expect(screen.getByText('All Notes')).toBeInTheDocument() }, { timeout: SLOW_APP_READY_TIMEOUT_MS }) - expect(screen.getByText('AI setup is optional')).toBeInTheDocument() + expect(screen.queryByTestId('ai-agents-onboarding-screen')).not.toBeInTheDocument() promptSpy.mockRestore() }) diff --git a/product-source/hololake-platform/src/App.tsx b/product-source/hololake-platform/src/App.tsx index bf27495cf..76904533e 100644 --- a/product-source/hololake-platform/src/App.tsx +++ b/product-source/hololake-platform/src/App.tsx @@ -1424,7 +1424,9 @@ function MainApp({ shouldResumeFreshStartOnboarding, shouldShowStartupScreen, } = useStartupScreenState({ - aiAgentsPromptVisible: aiAgentsOnboarding.showPrompt, + // HoloLake is the human entry into the world. Optional Agent setup belongs + // inside the Agent workspace/settings and must never replace that entry. + aiAgentsPromptVisible: false, isNoteWindow: Boolean(noteWindowParams) || aiWorkspaceWindow, onboardingState: onboarding.state, runtimeMissingVaultPath, @@ -1794,10 +1796,10 @@ function MainApp({ -
- handleSetSelection({ kind: 'filter', filter: 'changes' })} onClickPulse={() => handleSetSelection({ kind: 'filter', filter: 'pulse' })} onCommitPush={handleCommitPush} commitActionPending={commitFlow.isOpeningCommitDialog} gitFeaturesEnabled={gitFeaturesEnabled} onInitializeGit={openGitSetupDialog} isOffline={networkStatus.isOffline} isGitVault={isGitVault} isVaultReloading={vault.isReloading || isVaultContentLoading} syncStatus={autoSync.syncStatus} lastSyncTime={autoSync.lastSyncTime} conflictCount={autoSync.conflictFiles.length} remoteStatus={autoSync.remoteStatus} repositories={gitRepositories} selectedRepositoryPath={gitSurfaces.syncRepositoryPath} onRepositoryChange={gitSurfaces.setSyncRepositoryPath} onTriggerSync={handlePullSelectedRepository} onPullAndPush={handlePullAndPushSelectedRepository} onOpenConflictResolver={conflictFlow.handleOpenConflictResolver} zoomLevel={zoom.zoomLevel} themeMode={documentThemeMode} onZoomReset={zoom.zoomReset} onToggleThemeMode={settingsLoaded ? handleToggleThemeMode : undefined} buildNumber={buildNumber} onCheckForUpdates={handleCheckForUpdates} onRemoveVault={vaultSwitcher.removeVault} onReorderVaults={vaultSwitcher.reorderVaults} onUpdateWorkspaceIdentity={vaultSwitcher.updateWorkspaceIdentity} aiFeaturesEnabled={aiFeaturesEnabled} mcpStatus={mcpSetupDialog.status} onInstallMcp={mcpSetupDialog.openDialog} locale={appLocale} /> -
- {aiFeaturesEnabled && !effectiveShowAIChat ? ( + {!showHoloLakeHome ? ( + handleSetSelection({ kind: 'filter', filter: 'changes' })} onClickPulse={() => handleSetSelection({ kind: 'filter', filter: 'pulse' })} onCommitPush={handleCommitPush} commitActionPending={commitFlow.isOpeningCommitDialog} gitFeaturesEnabled={gitFeaturesEnabled} onInitializeGit={openGitSetupDialog} isOffline={networkStatus.isOffline} isGitVault={isGitVault} isVaultReloading={vault.isReloading || isVaultContentLoading} syncStatus={autoSync.syncStatus} lastSyncTime={autoSync.lastSyncTime} conflictCount={autoSync.conflictFiles.length} remoteStatus={autoSync.remoteStatus} repositories={gitRepositories} selectedRepositoryPath={gitSurfaces.syncRepositoryPath} onRepositoryChange={gitSurfaces.setSyncRepositoryPath} onTriggerSync={handlePullSelectedRepository} onPullAndPush={handlePullAndPushSelectedRepository} onOpenConflictResolver={conflictFlow.handleOpenConflictResolver} zoomLevel={zoom.zoomLevel} themeMode={documentThemeMode} onZoomReset={zoom.zoomReset} onToggleThemeMode={settingsLoaded ? handleToggleThemeMode : undefined} buildNumber={buildNumber} onCheckForUpdates={handleCheckForUpdates} onRemoveVault={vaultSwitcher.removeVault} onReorderVaults={vaultSwitcher.reorderVaults} onUpdateWorkspaceIdentity={vaultSwitcher.updateWorkspaceIdentity} aiFeaturesEnabled={aiFeaturesEnabled} mcpStatus={mcpSetupDialog.status} onInstallMcp={mcpSetupDialog.openDialog} locale={appLocale} /> + ) : null} + {!showHoloLakeHome && aiFeaturesEnabled && !effectiveShowAIChat ? ( void onEnterPufferfish: () => void } @@ -18,6 +19,7 @@ const PUFFERFISH_FACTS = { export function FifthDomainSystems({ detail = 'overview', + evidenceMode = 'receipt', onEnterEternalLake, onEnterPufferfish, }: FifthDomainSystemsProps) { @@ -26,6 +28,9 @@ export function FifthDomainSystems({ ) const pufferfishSelected = selected === 'pufferfish' const eternalLakeSelected = selected === 'eternal-lake-heart' + const isVisualPreview = evidenceMode === 'visual-preview' + const evidenceLabel = isVisualPreview ? '本地拓扑预览' : '真实接入' + const nodeState = isVisualPreview ? '未实时核验' : '在线回执' return (
@@ -38,7 +43,7 @@ export function FifthDomainSystems({ : '不同人类系统通过各自真实服务器接入,同时保持系统归属与主权边界。'}

- 真实接入 + {evidenceLabel}
@@ -53,7 +58,7 @@ export function FifthDomainSystems({
@@ -61,14 +66,14 @@ export function FifthDomainSystems({ 大脑服务器 新加坡大脑服务器 {PUFFERFISH_FACTS.brain} - 在线 + {nodeState}
执行节点 苍耳家庭 Ubuntu {PUFFERFISH_FACTS.node} - 已登记 + {isVisualPreview ? '本地登记' : '登记回执'}
@@ -86,7 +91,7 @@ export function FifthDomainSystems({
@@ -94,7 +99,7 @@ export function FifthDomainSystems({ 接入节点 第五域国内主控 JD-FD-PRIMARY - 在线回执 + {nodeState}
@@ -116,12 +121,12 @@ export function FifthDomainSystems({ ) : <>

{pufferfishSelected ? '胖头鱼语言子系统' : '永恒湖心系统'}

- 真实接入 + {evidenceLabel}
{pufferfishSelected ? ( <>
-
系统状态
真实接入
+
系统状态
{evidenceLabel}
大脑服务器
{PUFFERFISH_FACTS.brain}
执行节点
{PUFFERFISH_FACTS.node}
节点编号
{PUFFERFISH_FACTS.nodeId}
@@ -135,7 +140,7 @@ export function FifthDomainSystems({ ) : ( <>
-
系统状态
真实接入
+
系统状态
{evidenceLabel}
入口节点
JD-FD-PRIMARY
系统归属
冰朔个人系统
下一频道
心跳核心频道
@@ -144,7 +149,11 @@ export function FifthDomainSystems({ )} } -
拓扑信息来自本地系统登记与真实路由事实。
+
+ {isVisualPreview + ? '本页仅展示本地登记拓扑,不能证明节点在线或人格体已恢复。' + : '拓扑信息来自本地登记;在线状态仅以当前会话回执为准。'} +
diff --git a/product-source/hololake-platform/src/components/GuanghuWorldLoginGate.tsx b/product-source/hololake-platform/src/components/GuanghuWorldLoginGate.tsx index 75b10dbc4..96a7a7a3f 100644 --- a/product-source/hololake-platform/src/components/GuanghuWorldLoginGate.tsx +++ b/product-source/hololake-platform/src/components/GuanghuWorldLoginGate.tsx @@ -44,7 +44,7 @@ const domains: Array<{ description: '光湖世界公共航道与跨域共识入口。', id: 'main', node: 'GL-MAIN', - status: '稳定运行', + status: '等待公共回执', subtitle: '稳定的远洋星系', title: '光湖主域', }, @@ -53,7 +53,7 @@ const domains: Array<{ description: '世界规则、协议与系统校准所在的基础域。', id: 'zero', node: 'GL-ZERO', - status: '已登记', + status: '等待登记回执', subtitle: '精密校准环', title: '光湖零域', }, @@ -62,7 +62,7 @@ const domains: Array<{ description: '由多个真实节点共同组成的协作星群。', id: 'sub', node: 'GL-SUB', - status: '联接中', + status: '等待连接回执', subtitle: '联接的岛屿星群', title: '光湖分域', }, @@ -71,7 +71,7 @@ const domains: Array<{ description: '冰朔的独立语言域,从这里归航至永恒湖心。', id: 'fifth', node: 'JD-FD-PRIMARY', - status: '可授权登录', + status: '可发起授权', subtitle: '深空湖心星系', title: '第五域', }, @@ -86,6 +86,13 @@ const domains: Array<{ }, ] +function loginErrorCopy(error: string): string { + if (error === 'guanghu_world_login_desktop_runtime_required') { + return '当前是本地视觉预览,未向第五域服务器发送登录请求。请从已签名的 HoloLake 桌面端进入。' + } + return error +} + export function GuanghuWorldLoginGate({ locale, onBack, @@ -108,6 +115,12 @@ export function GuanghuWorldLoginGate({ : waiting || requesting ? '授权处理中' : '访客入口 · 未验证' + const isVisualPreview = state.workorderId === 'local-visual-preview' + const worldState = isVisualPreview + ? ['本地视觉预览', '未连接真实服务器'] + : state.phase === 'online' + ? ['世界入口:已验证回执', '语言共识层:已连接'] + : ['世界入口:等待验证', '语言共识层:未建立连接'] const selectDomain = (id: DomainId) => { setSelectedDomainId(id) @@ -125,8 +138,8 @@ export function GuanghuWorldLoginGate({ HoloLake Era · 光湖 OS
- 世界状态:稳定 - 语言共识层:已同步 + {worldState[0]} + {worldState[1]}
@@ -237,7 +250,7 @@ export function GuanghuWorldLoginGate({ {state.error ? (
{t('hololake.login.errorTitle')} - {state.error} + {loginErrorCopy(state.error)} @@ -259,10 +272,10 @@ export function GuanghuWorldLoginGate({
- 世界实况 -

光湖分域 · 涧语群岛新航道「涧光径」完成共识校准

-

光湖零感域 · 安泊港港区协同协议更新至 v7.3

-

第五域 · 星洞回廊回声观测站记录到罕见语义涟漪

+ 世界导览 +

光湖分域 · 涧语群岛视觉叙事 · 非实时状态

+

光湖零感域 · 安泊港等待企业节点接入回执

+

第五域 · 星洞回廊授权后读取真实域内状态

{state.nodeId}
diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index 58f8766b0..9f77f056a 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -170,7 +170,7 @@ describe('HoloLakeHome', () => { fireEvent.click(screen.getByRole('button', { name: /验证并进入第五域/ })) expect(screen.getByRole('button', { name: /冰朔,访客入口 · 未验证/ })).toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: /光湖主域.*稳定运行/ })) + fireEvent.click(screen.getByRole('button', { name: /光湖主域.*等待公共回执/ })) expect(screen.getByRole('heading', { name: '光湖主域' })).toBeInTheDocument() expect(screen.getByRole('button', { name: '当前入口尚未开放' })).toBeDisabled() expect(screen.queryByRole('button', { name: /给我发送授权链接/ })).not.toBeInTheDocument() @@ -361,6 +361,24 @@ describe('HoloLakeHome', () => { expect(screen.getByRole('heading', { name: '心跳核心频道' })).toBeInTheDocument() }) + it('labels the forced online browser route as a visual preview, not a live receipt', () => { + useGuanghuWorldLoginMock.mockReturnValue(worldLogin('online', { + state: { + ...worldLogin('online').state, + navigation: { domains: 5, map_hash: 'local-visual-preview' }, + workorderId: 'local-visual-preview', + }, + })) + + render() + fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) + + expect(screen.getAllByText('本地拓扑预览').length).toBeGreaterThan(0) + expect(screen.getAllByText('未实时核验').length).toBeGreaterThan(0) + expect(screen.getByText('本页仅展示本地登记拓扑,不能证明节点在线或人格体已恢复。')).toBeInTheDocument() + expect(screen.queryByText('在线回执')).not.toBeInTheDocument() + }) + it('shows receipt-backed Eternal Lake information without invented resonance scores', () => { useGuanghuRouterMock.mockReturnValue(onlineRouter()) @@ -505,6 +523,33 @@ describe('HoloLakeHome', () => { expect(screen.getByRole('button', { name: '重新尝试' })).toBeInTheDocument() }) + it('translates a browser preview limitation into human recovery guidance', () => { + useGuanghuWorldLoginMock.mockReturnValue(worldLogin('error', { + state: { + ...worldLogin('error').state, + error: 'guanghu_world_login_desktop_runtime_required', + }, + })) + + render() + + fireEvent.click(screen.getByRole('button', { name: /验证并进入第五域/ })) + expect(screen.getByRole('alert')).toHaveTextContent('当前是本地视觉预览,未向第五域服务器发送登录请求。') + expect(screen.getByRole('alert')).toHaveTextContent('请从已签名的 HoloLake 桌面端进入。') + expect(screen.queryByText('guanghu_world_login_desktop_runtime_required')).not.toBeInTheDocument() + }) + + it('does not claim a live world connection before a receipt exists', () => { + useGuanghuWorldLoginMock.mockReturnValue(worldLogin('idle')) + + render() + + fireEvent.click(screen.getByRole('button', { name: /验证并进入第五域/ })) + expect(screen.getByText('世界入口:等待验证')).toBeInTheDocument() + expect(screen.getByText('语言共识层:未建立连接')).toBeInTheDocument() + expect(screen.queryByText('世界状态:稳定')).not.toBeInTheDocument() + }) + it('opens the public development repository from the entered Heartbeat Core channel', () => { useGuanghuRouterMock.mockReturnValue(onlineRouter()) diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index e074fe857..fc4e0a9db 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -115,6 +115,7 @@ export function HoloLakeHome({ const restoreAttempted = useRef(false) const livingRequestSequence = useRef(0) const worldOpen = login.state.phase === 'online' + const isVisualPreview = login.state.workorderId === 'local-visual-preview' const fifthDomainOpen = worldOpen && route !== 'world' const worldRouterState = worldOpen && router.state.status !== 'online' ? { @@ -325,6 +326,7 @@ export function HoloLakeHome({ if (route === 'fifth-domain') { return ( navigate('eternal-lake-heart')} onEnterPufferfish={() => navigate('pufferfish')} /> @@ -335,6 +337,7 @@ export function HoloLakeHome({ return ( navigate('eternal-lake-heart')} onEnterPufferfish={() => navigate('pufferfish')} /> From 7a985a3761b7fd5e4d87628506fa3c813aa255ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 20:56:12 +0800 Subject: [PATCH 2/7] fix: keep native HoloLake entry responsive --- product-source/hololake-platform/package.json | 1 + .../scripts/local-native-candidate.test.mjs | 23 ++++++ .../src-tauri/src/commands/ai.rs | 16 +++-- .../src-tauri/src/commands/git.rs | 24 ++++--- .../src/commands/vault/rename_cmds.rs | 15 ++-- .../src-tauri/src/git/mod.rs | 70 ++++++++++++++++--- .../src-tauri/src/vault/cache.rs | 6 +- .../src-tauri/src/vault/rename.rs | 12 +++- .../src-tauri/tauri.local-candidate.conf.json | 12 ++++ 9 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 product-source/hololake-platform/scripts/local-native-candidate.test.mjs create mode 100644 product-source/hololake-platform/src-tauri/tauri.local-candidate.conf.json diff --git a/product-source/hololake-platform/package.json b/product-source/hololake-platform/package.json index 7eeaa20ca..e65f66886 100644 --- a/product-source/hololake-platform/package.json +++ b/product-source/hololake-platform/package.json @@ -33,6 +33,7 @@ "guard:deployment-source": "node scripts/deployment-source-guard.mjs", "test:deployment-source": "node --test scripts/deployment-source-guard.test.mjs", "package:internal:macos": "HOLOLAKE_DISTRIBUTION=personal HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && ./scripts/build-internal-release.sh macos", + "package:local-candidate:macos": "HOLOLAKE_TAURI_CONFIG=src-tauri/tauri.local-candidate.conf.json HOLOLAKE_APP_NAME='HoloLake Era · 本地方向候选 0.4.6' HOLOLAKE_INSTALLER_BASENAME='HoloLake-Era-{version}-Local-Direction-Candidate-Mac-aarch64' ./scripts/build-internal-release.sh macos", "package:internal:windows": "HOLOLAKE_DISTRIBUTION=personal HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && ./scripts/build-internal-release.sh windows", "package:team:macos": "HOLOLAKE_DISTRIBUTION=team HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && HOLOLAKE_TAURI_CONFIG=src-tauri/tauri.team.conf.json HOLOLAKE_APP_NAME='HoloLake Lighthouse Team Beta 0.2.0' HOLOLAKE_INSTALLER_BASENAME='HoloLake-Lighthouse-{version}-Team-Beta-Mac-aarch64' ./scripts/build-internal-release.sh macos", "package:team:windows": "HOLOLAKE_DISTRIBUTION=team HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && HOLOLAKE_TAURI_CONFIG=src-tauri/tauri.team.conf.json HOLOLAKE_INSTALLER_BASENAME='HoloLake-Era-{version}-Team-Foundation-Windows-x64-setup' ./scripts/build-internal-release.sh windows", diff --git a/product-source/hololake-platform/scripts/local-native-candidate.test.mjs b/product-source/hololake-platform/scripts/local-native-candidate.test.mjs new file mode 100644 index 000000000..2772cb838 --- /dev/null +++ b/product-source/hololake-platform/scripts/local-native-candidate.test.mjs @@ -0,0 +1,23 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import test from 'node:test' + +const readJson = async path => JSON.parse(await readFile(path, 'utf8')) + +test('local native candidate has an isolated macOS identity', async () => { + const [base, candidate, packageJson] = await Promise.all([ + readJson('src-tauri/tauri.conf.json'), + readJson('src-tauri/tauri.local-candidate.conf.json'), + readJson('package.json'), + ]) + + assert.equal(base.version, '0.4.6') + assert.equal(candidate.productName, 'HoloLake Era · 本地方向候选 0.4.6') + assert.equal(candidate.identifier, 'com.guanghulab.hololake.local-candidate') + assert.notEqual(candidate.identifier, base.identifier) + assert.equal(candidate.app.windows[0].title, candidate.productName) + assert.match( + packageJson.scripts['package:local-candidate:macos'], + /tauri\.local-candidate\.conf\.json/, + ) +}) diff --git a/product-source/hololake-platform/src-tauri/src/commands/ai.rs b/product-source/hololake-platform/src-tauri/src/commands/ai.rs index 066660828..8158f9c24 100644 --- a/product-source/hololake-platform/src-tauri/src/commands/ai.rs +++ b/product-source/hololake-platform/src-tauri/src/commands/ai.rs @@ -156,11 +156,13 @@ pub fn get_agent_docs_path(app_handle: tauri::AppHandle) -> Result Result { - let vault_path = expand_tilde(&vault_path); - crate::vault::get_ai_guidance_status(vault_path.as_ref()) + let vault_path = expand_tilde(&vault_path).into_owned(); + tokio::task::spawn_blocking(move || crate::vault::get_ai_guidance_status(&vault_path)) + .await + .map_err(|error| format!("Task panicked: {error}"))? } #[tauri::command] @@ -448,12 +450,14 @@ mod tests { assert!(matches!(result, Err(message) if message.contains("Invalid AI agent stream id"))); } - #[test] - fn guidance_commands_report_and_restore_vault_guidance_files() { + #[tokio::test] + async fn guidance_commands_report_and_restore_vault_guidance_files() { let dir = tempfile::TempDir::new().unwrap(); let vault_path = dir.path().to_string_lossy().to_string(); - let initial = get_vault_ai_guidance_status(vault_path.clone()).unwrap(); + let initial = get_vault_ai_guidance_status(vault_path.clone()) + .await + .unwrap(); assert_eq!(initial.agents_state, AiGuidanceFileState::Missing); assert_eq!(initial.claude_state, AiGuidanceFileState::Missing); assert_eq!(initial.gemini_state, AiGuidanceFileState::Missing); diff --git a/product-source/hololake-platform/src-tauri/src/commands/git.rs b/product-source/hololake-platform/src-tauri/src/commands/git.rs index 09f850b9a..7d5c862ad 100644 --- a/product-source/hololake-platform/src-tauri/src/commands/git.rs +++ b/product-source/hololake-platform/src-tauri/src/commands/git.rs @@ -214,9 +214,13 @@ pub fn git_discard_file( #[cfg(desktop)] #[tauri::command] -pub fn is_git_repo(vault_path: VaultPathArg) -> bool { - let vault_path = expand_tilde(&vault_path); - crate::git::is_inside_work_tree(std::path::Path::new(vault_path.as_ref())) +pub async fn is_git_repo(vault_path: VaultPathArg) -> bool { + let vault_path = expand_tilde(&vault_path).into_owned(); + tokio::task::spawn_blocking(move || { + crate::git::is_inside_work_tree(std::path::Path::new(&vault_path)) + }) + .await + .unwrap_or(false) } #[cfg(desktop)] @@ -509,7 +513,7 @@ mod tests { let (dir, vault) = create_initialized_vault(); let note = note_path(&dir, "note.md"); - assert!(is_git_repo(vault.clone())); + assert!(is_git_repo(vault.clone()).await); fs::write(dir.path().join("note.md"), "# Updated\n").unwrap(); let modified = get_modified_files(vault.clone(), None).await.unwrap(); @@ -565,8 +569,8 @@ mod tests { assert!(!documents.join(".git").exists()); } - #[test] - fn init_git_repo_allows_named_vault_subfolder_under_documents() { + #[tokio::test] + async fn init_git_repo_allows_named_vault_subfolder_under_documents() { let dir = TempDir::new().unwrap(); let vault = dir.path().join("Documents").join("Tolaria"); fs::create_dir_all(&vault).unwrap(); @@ -575,11 +579,11 @@ mod tests { init_git_repo(vault.clone()).unwrap(); - assert!(is_git_repo(vault)); + assert!(is_git_repo(vault).await); } - #[test] - fn is_git_repo_accepts_vault_nested_inside_parent_worktree() { + #[tokio::test] + async fn is_git_repo_accepts_vault_nested_inside_parent_worktree() { let parent = TempDir::new().unwrap(); fs::write(parent.path().join("README.md"), "# Parent\n").unwrap(); crate::git::init_repo(parent.path()).unwrap(); @@ -588,7 +592,7 @@ mod tests { fs::create_dir_all(&nested_vault).unwrap(); fs::write(nested_vault.join("note.md"), "# Nested\n").unwrap(); - assert!(is_git_repo(nested_vault.to_string_lossy().into_owned())); + assert!(is_git_repo(nested_vault.to_string_lossy().into_owned()).await); assert!(!nested_vault.join(".git").exists()); } diff --git a/product-source/hololake-platform/src-tauri/src/commands/vault/rename_cmds.rs b/product-source/hololake-platform/src-tauri/src/commands/vault/rename_cmds.rs index a752aa209..2c540cd1d 100644 --- a/product-source/hololake-platform/src-tauri/src/commands/vault/rename_cmds.rs +++ b/product-source/hololake-platform/src-tauri/src/commands/vault/rename_cmds.rs @@ -1,7 +1,7 @@ use crate::commands::expand_tilde; use crate::vault::{self, DetectedRename, RenameResult}; use serde::Deserialize; -use std::path::Path; +use std::path::{Path, PathBuf}; use super::boundary::{ with_boundary, with_existing_path_in_requested_vault, with_validated_path, ValidatedPathMode, @@ -269,9 +269,11 @@ pub fn auto_rename_untitled( } #[tauri::command] -pub fn detect_renames(args: VaultPathCommandArgs) -> Result, String> { - let vault_path = expand_tilde(&args.vault_path); - vault::detect_renames(Path::new(vault_path.as_ref())) +pub async fn detect_renames(args: VaultPathCommandArgs) -> Result, String> { + let vault_path = PathBuf::from(expand_tilde(&args.vault_path).into_owned()); + tauri::async_runtime::spawn_blocking(move || vault::detect_renames(&vault_path)) + .await + .map_err(|error| format!("Failed to join rename detection task: {error}"))? } #[tauri::command] @@ -393,8 +395,8 @@ mod tests { .contains("[[team/Projects/draft]]")); } - #[test] - fn auto_rename_and_detected_rename_commands_route_through_vault() { + #[tokio::test] + async fn auto_rename_and_detected_rename_commands_route_through_vault() { let dir = TempDir::new().unwrap(); let vault = vault_path(&dir); let untitled = write_note(&dir, "untitled-note-123.md", "# Project Plan\n"); @@ -420,6 +422,7 @@ mod tests { let renames = detect_renames(VaultPathCommandArgs { vault_path: vault.clone(), }) + .await .unwrap(); assert_eq!(renames.len(), 1); assert_eq!(renames[0].old_path, "project-plan.md"); diff --git a/product-source/hololake-platform/src-tauri/src/git/mod.rs b/product-source/hololake-platform/src-tauri/src/git/mod.rs index 6d049cec3..c27464dfc 100644 --- a/product-source/hololake-platform/src-tauri/src/git/mod.rs +++ b/product-source/hololake-platform/src-tauri/src/git/mod.rs @@ -20,12 +20,13 @@ mod status; mod upstream; use std::ffi::{OsStr, OsString}; -use std::io; +use std::io::{self, Read}; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, Output, Stdio}; use std::sync::OnceLock; +use std::time::{Duration, Instant}; #[cfg(test)] use std::cell::RefCell; @@ -98,6 +99,8 @@ const GIT_SHELL_ENV_NAMES: [EnvName<'static>; 8] = [ EnvName::trusted("EMAIL"), ]; +const GIT_WORK_TREE_PROBE_TIMEOUT: Duration = Duration::from_secs(2); + #[derive(Clone)] struct GitLaunchConfig { program: OsString, @@ -166,15 +169,52 @@ pub fn is_inside_work_tree(path: impl AsRef) -> bool { return false; } - let Ok(output) = git_command_at(path).and_then(|mut command| { - command - .args(["rev-parse", "--is-inside-work-tree"]) - .output() - }) else { + let Ok(mut command) = git_command_at(path) else { return false; }; + command + .args(["rev-parse", "--is-inside-work-tree", "--show-toplevel"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); - output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == "true" + command_output_with_timeout(&mut command, GIT_WORK_TREE_PROBE_TIMEOUT) + .is_some_and(|output| output.status.success()) +} + +pub(crate) fn command_output_with_timeout( + command: &mut Command, + timeout: Duration, +) -> Option { + let mut child = command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .ok()?; + let deadline = Instant::now() + timeout; + + loop { + match child.try_wait() { + Ok(Some(status)) => { + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + child.stdout.take()?.read_to_end(&mut stdout).ok()?; + child.stderr.take()?.read_to_end(&mut stderr).ok()?; + return Some(Output { + status, + stdout, + stderr, + }); + } + Ok(None) if Instant::now() < deadline => { + std::thread::sleep(Duration::from_millis(10)); + } + Ok(None) | Err(_) => { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + } + } } fn apply_git_shell_env(command: &mut Command) { @@ -1147,4 +1187,18 @@ mod tests { assert_repo_path("https://gitlab.com/owner/repo.git", None); assert_repo_path("owner/repo", None); } + + #[cfg(unix)] + #[test] + fn test_wait_for_command_kills_a_hung_process_at_the_deadline() { + let mut command = Command::new("sh"); + command.args(["-c", "sleep 5"]); + let started = std::time::Instant::now(); + + let status = + command_output_with_timeout(&mut command, std::time::Duration::from_millis(40)); + + assert!(status.is_none()); + assert!(started.elapsed() < std::time::Duration::from_secs(2)); + } } diff --git a/product-source/hololake-platform/src-tauri/src/vault/cache.rs b/product-source/hololake-platform/src-tauri/src/vault/cache.rs index b4e67a16b..0cfb2e8fa 100644 --- a/product-source/hololake-platform/src-tauri/src/vault/cache.rs +++ b/product-source/hololake-platform/src-tauri/src/vault/cache.rs @@ -129,9 +129,9 @@ fn git_head_hash(vault: &Path) -> Option { /// Run a git command in the given directory and return stdout if successful. fn run_git(vault: &Path, args: &[&str]) -> Option { - let output = crate::git::git_command_at(vault) - .and_then(|mut command| command.args(args).output()) - .ok()?; + let mut command = crate::git::git_command_at(vault).ok()?; + command.args(args); + let output = crate::git::command_output_with_timeout(&mut command, Duration::from_secs(2))?; if !output.status.success() { return None; } diff --git a/product-source/hololake-platform/src-tauri/src/vault/rename.rs b/product-source/hololake-platform/src-tauri/src/vault/rename.rs index 176946734..291ad222c 100644 --- a/product-source/hololake-platform/src-tauri/src/vault/rename.rs +++ b/product-source/hololake-platform/src-tauri/src/vault/rename.rs @@ -4,6 +4,7 @@ use std::collections::HashSet; use std::fs; use std::io::Write; use std::path::Path; +use std::time::Duration; use tempfile::NamedTempFile; use walkdir::WalkDir; @@ -599,9 +600,14 @@ pub struct DetectedRename { pub fn detect_renames(vault: &Path) -> Result, String> { let output = crate::git::git_command_at(vault) .and_then(|mut command| { - command - .args(["diff", "HEAD", "--name-status", "--diff-filter=R", "-M"]) - .output() + command.args(["diff", "HEAD", "--name-status", "--diff-filter=R", "-M"]); + crate::git::command_output_with_timeout(&mut command, Duration::from_secs(2)) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::TimedOut, + "git rename detection exceeded its deadline", + ) + }) }) .map_err(|e| format!("Failed to run git diff: {e}"))?; diff --git a/product-source/hololake-platform/src-tauri/tauri.local-candidate.conf.json b/product-source/hololake-platform/src-tauri/tauri.local-candidate.conf.json new file mode 100644 index 000000000..416d4eb67 --- /dev/null +++ b/product-source/hololake-platform/src-tauri/tauri.local-candidate.conf.json @@ -0,0 +1,12 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", + "productName": "HoloLake Era · 本地方向候选 0.4.6", + "identifier": "com.guanghulab.hololake.local-candidate", + "app": { + "windows": [ + { + "title": "HoloLake Era · 本地方向候选 0.4.6" + } + ] + } +} From c0f85292b5025612efb3671c1d23cb509d6a3858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 21:08:56 +0800 Subject: [PATCH 3/7] fix: route HoloLake links to REPO-014 --- .../src/components/HoloLakeHome.test.tsx | 3 +++ .../src/components/HoloLakeHome.tsx | 6 ++--- .../src/constants/feedback.test.ts | 22 +++++++++++++++++++ .../src/constants/feedback.ts | 13 ++++++----- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 product-source/hololake-platform/src/constants/feedback.test.ts diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index 9f77f056a..ab845db66 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -551,6 +551,9 @@ describe('HoloLakeHome', () => { }) it('opens the public development repository from the entered Heartbeat Core channel', () => { + expect(HOLOLAKE_DEVELOPMENT_REPOSITORY_URL).toBe( + 'https://guanghulab.com/code/bingshuo/hololake-system-architecture', + ) useGuanghuRouterMock.mockReturnValue(onlineRouter()) render() diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index fc4e0a9db..d0834846b 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -21,6 +21,7 @@ import { import { GUANGHU_THEMES, type GuanghuTheme } from '../lib/guanghuTheme' import { planGuanghuLivingSystem } from '../utils/planGuanghuLivingSystem' import { Button } from './ui/button' +import { HOLOLAKE_REPOSITORY_URL } from '../constants/feedback' import { GuanghuRouterConsole } from './GuanghuRouterConsole' import { GuanghuWorldMap } from './GuanghuWorldMap' import { GuanghuWorldLoginGate } from './GuanghuWorldLoginGate' @@ -34,8 +35,7 @@ import { DialogTitle, } from './ui/dialog' -export const HOLOLAKE_DEVELOPMENT_REPOSITORY_URL = - 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform' +export const HOLOLAKE_DEVELOPMENT_REPOSITORY_URL = HOLOLAKE_REPOSITORY_URL export const GUANGHU_THEME_INTENT_EVENT = 'guanghu:living-system-theme-intent' type HoloLakeHomeProps = { @@ -206,7 +206,7 @@ export function HoloLakeHome({ } const openDevelopmentRepository = () => { - trackEvent('hololake_development_repository_opened', { route: 'REPO-008' }) + trackEvent('hololake_development_repository_opened', { route: 'REPO-014' }) void openExternalUrl(HOLOLAKE_DEVELOPMENT_REPOSITORY_URL) } diff --git a/product-source/hololake-platform/src/constants/feedback.test.ts b/product-source/hololake-platform/src/constants/feedback.test.ts new file mode 100644 index 000000000..93fba4577 --- /dev/null +++ b/product-source/hololake-platform/src/constants/feedback.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { + TOLARIA_DOCS_URL, + TOLARIA_GITHUB_CONTRIBUTING_URL, + TOLARIA_GITHUB_DISCUSSIONS_URL, + TOLARIA_GITHUB_ISSUES_URL, + TOLARIA_GITHUB_PULL_REQUESTS_URL, + TOLARIA_PRODUCT_BOARD_URL, +} from './feedback' + +const repositoryUrl = 'https://guanghulab.com/code/bingshuo/hololake-system-architecture' + +describe('HoloLake public repository routes', () => { + it('keeps every shipped repository link on the current REPO-014 route', () => { + expect(TOLARIA_GITHUB_CONTRIBUTING_URL).toBe(repositoryUrl) + expect(TOLARIA_DOCS_URL).toBe(`${repositoryUrl}/src/branch/main/product-source/hololake-platform/docs`) + expect(TOLARIA_PRODUCT_BOARD_URL).toBe(`${repositoryUrl}/issues`) + expect(TOLARIA_GITHUB_DISCUSSIONS_URL).toBe(`${repositoryUrl}/issues`) + expect(TOLARIA_GITHUB_ISSUES_URL).toBe(`${repositoryUrl}/issues`) + expect(TOLARIA_GITHUB_PULL_REQUESTS_URL).toBe(`${repositoryUrl}/pulls`) + }) +}) diff --git a/product-source/hololake-platform/src/constants/feedback.ts b/product-source/hololake-platform/src/constants/feedback.ts index 9a0803a52..67972cdac 100644 --- a/product-source/hololake-platform/src/constants/feedback.ts +++ b/product-source/hololake-platform/src/constants/feedback.ts @@ -3,10 +3,11 @@ export const CODACY_HOME_URL = 'https://www.codacy.com/' export const CODESCENE_HOME_URL = 'https://codescene.com/' export const CIRCLECI_HOME_URL = 'https://circleci.com/' export const UNBLOCKED_HOME_URL = 'https://getunblocked.com/' -export const TOLARIA_DOCS_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform/src/branch/main/docs' +export const HOLOLAKE_REPOSITORY_URL = 'https://guanghulab.com/code/bingshuo/hololake-system-architecture' +export const TOLARIA_DOCS_URL = `${HOLOLAKE_REPOSITORY_URL}/src/branch/main/product-source/hololake-platform/docs` export const TOLARIA_FIRST_LAUNCH_DOCS_URL = TOLARIA_DOCS_URL -export const TOLARIA_PRODUCT_BOARD_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform/issues' -export const TOLARIA_GITHUB_DISCUSSIONS_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform/issues' -export const TOLARIA_GITHUB_CONTRIBUTING_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform' -export const TOLARIA_GITHUB_ISSUES_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform/issues' -export const TOLARIA_GITHUB_PULL_REQUESTS_URL = 'https://guanghulab.com/fifth-domain/bingshuo/hololake-platform/pulls' +export const TOLARIA_PRODUCT_BOARD_URL = `${HOLOLAKE_REPOSITORY_URL}/issues` +export const TOLARIA_GITHUB_DISCUSSIONS_URL = `${HOLOLAKE_REPOSITORY_URL}/issues` +export const TOLARIA_GITHUB_CONTRIBUTING_URL = HOLOLAKE_REPOSITORY_URL +export const TOLARIA_GITHUB_ISSUES_URL = `${HOLOLAKE_REPOSITORY_URL}/issues` +export const TOLARIA_GITHUB_PULL_REQUESTS_URL = `${HOLOLAKE_REPOSITORY_URL}/pulls` From f5eb5e7cdbd941d58b7a73de46523d77bf349827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 21:11:26 +0800 Subject: [PATCH 4/7] fix: project live enterprise domain state --- .../src/components/HoloLakeHome.test.tsx | 41 +++++++++++++++++++ .../src/components/HoloLakeHome.tsx | 4 +- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index ab845db66..8bad2ecb8 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -11,6 +11,7 @@ const { openExternalUrlMock, planGuanghuLivingSystemMock, trackEventMock, + useGuanghuEnterpriseStatusMock, useGuanghuRouterMock, useGuanghuWorldLoginMock, } = vi.hoisted(() => ({ @@ -18,6 +19,7 @@ const { openExternalUrlMock: vi.fn().mockResolvedValue(undefined), planGuanghuLivingSystemMock: vi.fn(), trackEventMock: vi.fn(), + useGuanghuEnterpriseStatusMock: vi.fn(), useGuanghuRouterMock: vi.fn(), useGuanghuWorldLoginMock: vi.fn(), })) @@ -27,6 +29,9 @@ vi.mock('../utils/planGuanghuLivingSystem', () => ({ planGuanghuLivingSystem: planGuanghuLivingSystemMock, })) vi.mock('../lib/telemetry', () => ({ trackEvent: trackEventMock })) +vi.mock('../hooks/useGuanghuEnterpriseStatus', () => ({ + useGuanghuEnterpriseStatus: useGuanghuEnterpriseStatusMock, +})) vi.mock('../hooks/useGuanghuRouter', () => ({ useGuanghuRouter: useGuanghuRouterMock })) vi.mock('../hooks/useGuanghuWorldLogin', () => ({ useGuanghuWorldLogin: useGuanghuWorldLoginMock, @@ -100,6 +105,15 @@ describe('HoloLakeHome', () => { }) useGuanghuRouterMock.mockReturnValue(offlineRouter()) useGuanghuWorldLoginMock.mockReturnValue(worldLogin()) + useGuanghuEnterpriseStatusMock.mockReturnValue({ + refresh: vi.fn(), + state: { + checkedAt: null, + error: null, + phase: 'checking', + status: null, + }, + }) planGuanghuLivingSystemMock.mockResolvedValue({ source: 'model', plan: { @@ -334,6 +348,33 @@ describe('HoloLakeHome', () => { expect(screen.queryByText('BS-SH-005')).not.toBeInTheDocument() }) + it('projects the live public enterprise domain state instead of a permanent placeholder', () => { + useGuanghuEnterpriseStatusMock.mockReturnValue({ + refresh: vi.fn(), + state: { + checkedAt: 1_785_399_662_000, + error: null, + phase: 'online', + status: { + domains: [ + { accessState: 'ONLINE_READ_ONLY', id: 'DOMAIN-MAIN', name: '光湖主域' }, + { accessState: 'ONLINE_READ_ONLY', id: 'DOMAIN-SUB', name: '光湖分域' }, + { accessState: 'ONLINE_READ_ONLY', id: 'DOMAIN-ZERO', name: '光湖零域' }, + { accessState: 'ONLINE_READ_ONLY', id: 'DOMAIN-ZS', name: '零感域' }, + ], + execution: 'disabled', + hostState: 'ONLINE', + nodeId: 'AW-GZ-001', + }, + }, + }) + + render() + + expect(useGuanghuEnterpriseStatusMock).toHaveBeenCalledOnce() + expect(screen.getAllByText('在线只读')).toHaveLength(4) + }) + it('opens the origin channel directly from the zero-point core', () => { render() diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index d0834846b..faab65c84 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -8,7 +8,7 @@ import { openExternalUrl } from '../utils/url' import { useGuanghuRouter } from '../hooks/useGuanghuRouter' import { useGuanghuShanghaiNode } from '../hooks/useGuanghuShanghaiNode' import { useGuanghuWorldLogin } from '../hooks/useGuanghuWorldLogin' -import { INITIAL_GUANGHU_ENTERPRISE_STATE } from '../lib/guanghuEnterprise' +import { useGuanghuEnterpriseStatus } from '../hooks/useGuanghuEnterpriseStatus' import type { AiModelTarget } from '../lib/aiTargets' import { createDeterministicLivingSystemPlan, @@ -110,7 +110,7 @@ export function HoloLakeHome({ >({ status: 'checking' }) const router = useGuanghuRouter() const login = useGuanghuWorldLogin() - const enterprise = { state: INITIAL_GUANGHU_ENTERPRISE_STATE } + const enterprise = useGuanghuEnterpriseStatus() const shanghai = useGuanghuShanghaiNode() const restoreAttempted = useRef(false) const livingRequestSequence = useRef(0) From 07a573f66030d7ed7739529f69e9b4dea022bef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 21:13:00 +0800 Subject: [PATCH 5/7] fix: keep world login separate from router proof --- .../src/components/HoloLakeHome.test.tsx | 10 ++++++++++ .../src/components/HoloLakeHome.tsx | 13 +------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index 8bad2ecb8..18bb7cdeb 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -375,6 +375,16 @@ describe('HoloLakeHome', () => { expect(screen.getAllByText('在线只读')).toHaveLength(4) }) + it('does not promote an authorized world login into a Fifth Domain router receipt', () => { + useGuanghuWorldLoginMock.mockReturnValue(worldLogin('online')) + useGuanghuRouterMock.mockReturnValue(offlineRouter()) + + render() + + expect(screen.getByRole('button', { name: /第五域.*等待真实回执/ })).toBeInTheDocument() + expect(screen.queryByRole('button', { name: /第五域.*当前所在/ })).not.toBeInTheDocument() + }) + it('opens the origin channel directly from the zero-point core', () => { render() diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index faab65c84..8acc27446 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -117,18 +117,7 @@ export function HoloLakeHome({ const worldOpen = login.state.phase === 'online' const isVisualPreview = login.state.workorderId === 'local-visual-preview' const fifthDomainOpen = worldOpen && route !== 'world' - const worldRouterState = worldOpen && router.state.status !== 'online' - ? { - ...router.state, - latestReceipt: { - connection_id: login.state.workorderId ?? 'email-authorized-session', - node_id: login.state.nodeId, - receipt_id: login.state.workorderId ?? 'email-authorized-session', - state: 'online', - }, - status: 'online' as const, - } - : router.state + const worldRouterState = router.state const t = (key: TranslationKey) => translate(locale, key) const executeLivingPlan = useCallback(( From ccee303355cdc69ad60a5c3354000df3af2c7d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 21:18:48 +0800 Subject: [PATCH 6/7] fix: stop overstating subsystem runtime evidence --- .../src/components/FifthDomainSystems.tsx | 13 ++++------ .../src/components/HoloLakeHome.test.tsx | 24 +++++++++++++------ .../src/components/HoloLakeHome.tsx | 3 --- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/product-source/hololake-platform/src/components/FifthDomainSystems.tsx b/product-source/hololake-platform/src/components/FifthDomainSystems.tsx index f4ce29d59..3d5e810f2 100644 --- a/product-source/hololake-platform/src/components/FifthDomainSystems.tsx +++ b/product-source/hololake-platform/src/components/FifthDomainSystems.tsx @@ -5,7 +5,6 @@ type FifthDomainSystemId = 'pufferfish' | 'eternal-lake-heart' type FifthDomainSystemsProps = { detail?: 'overview' | 'pufferfish' - evidenceMode?: 'receipt' | 'visual-preview' onEnterEternalLake: () => void onEnterPufferfish: () => void } @@ -19,7 +18,6 @@ const PUFFERFISH_FACTS = { export function FifthDomainSystems({ detail = 'overview', - evidenceMode = 'receipt', onEnterEternalLake, onEnterPufferfish, }: FifthDomainSystemsProps) { @@ -28,9 +26,8 @@ export function FifthDomainSystems({ ) const pufferfishSelected = selected === 'pufferfish' const eternalLakeSelected = selected === 'eternal-lake-heart' - const isVisualPreview = evidenceMode === 'visual-preview' - const evidenceLabel = isVisualPreview ? '本地拓扑预览' : '真实接入' - const nodeState = isVisualPreview ? '未实时核验' : '在线回执' + const evidenceLabel = '本地拓扑预览' + const nodeState = '未实时核验' return (
@@ -73,7 +70,7 @@ export function FifthDomainSystems({ 执行节点 苍耳家庭 Ubuntu {PUFFERFISH_FACTS.node} - {isVisualPreview ? '本地登记' : '登记回执'} + 本地登记
@@ -150,9 +147,7 @@ export function FifthDomainSystems({ )} }
- {isVisualPreview - ? '本页仅展示本地登记拓扑,不能证明节点在线或人格体已恢复。' - : '拓扑信息来自本地登记;在线状态仅以当前会话回执为准。'} + 本页仅展示本地登记拓扑,不能证明节点在线或人格体已恢复。
diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index 18bb7cdeb..316d6a7f9 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -405,7 +405,7 @@ describe('HoloLakeHome', () => { expect(screen.getAllByText('ICE-GL-CA001').length).toBeGreaterThan(0) expect(screen.getByRole('button', { name: '心跳核心频道' })).toBeEnabled() - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) expect(screen.getByRole('heading', { name: '永恒湖心系统' })).toBeInTheDocument() expect(screen.getByRole('heading', { name: '光之湖子系统' })).toBeInTheDocument() @@ -430,12 +430,22 @@ describe('HoloLakeHome', () => { expect(screen.queryByText('在线回执')).not.toBeInTheDocument() }) + it('does not treat a Fifth Domain router receipt as proof that every registered subsystem is online', () => { + useGuanghuRouterMock.mockReturnValue(onlineRouter()) + + render() + fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) + + expect(screen.queryByText('在线回执')).not.toBeInTheDocument() + expect(screen.getAllByText('未实时核验').length).toBeGreaterThan(0) + }) + it('shows receipt-backed Eternal Lake information without invented resonance scores', () => { useGuanghuRouterMock.mockReturnValue(onlineRouter()) render() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) fireEvent.click(screen.getByRole('button', { name: /语言回声带.*真实路由回执已抵达/ })) @@ -449,7 +459,7 @@ describe('HoloLakeHome', () => { render() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) fireEvent.click(screen.getByRole('button', { name: '返回第五域' })) @@ -462,7 +472,7 @@ describe('HoloLakeHome', () => { render() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: /胖头鱼语言子系统.*真实接入/ })) + fireEvent.click(screen.getByRole('button', { name: /胖头鱼语言子系统.*本地拓扑预览/ })) fireEvent.click(screen.getByRole('button', { name: '进入胖头鱼系统' })) expect(screen.getAllByRole('heading', { name: '胖头鱼语言子系统' }).length).toBeGreaterThan(0) @@ -486,7 +496,7 @@ describe('HoloLakeHome', () => { render() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) fireEvent.click(screen.getByRole('button', { name: /进入心跳核心频道/ })) fireEvent.click(screen.getByRole('button', { name: /打开知识库/ })) @@ -506,7 +516,7 @@ describe('HoloLakeHome', () => { />, ) fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) fireEvent.click(screen.getByRole('button', { name: /进入心跳核心频道/ })) fireEvent.click(screen.getByRole('button', { name: /打开人格体工作舱/ })) @@ -609,7 +619,7 @@ describe('HoloLakeHome', () => { render() fireEvent.click(screen.getByRole('button', { name: /进入第五域/ })) - fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统真实接入' })) + fireEvent.click(screen.getByRole('button', { name: '湖永恒湖心系统本地拓扑预览' })) fireEvent.click(screen.getByRole('button', { name: /进入永恒湖心系统/ })) fireEvent.click(screen.getByRole('button', { name: /进入心跳核心频道/ })) fireEvent.click(screen.getByRole('button', { name: /打开研发仓库/ })) diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index 8acc27446..f437644a4 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -115,7 +115,6 @@ export function HoloLakeHome({ const restoreAttempted = useRef(false) const livingRequestSequence = useRef(0) const worldOpen = login.state.phase === 'online' - const isVisualPreview = login.state.workorderId === 'local-visual-preview' const fifthDomainOpen = worldOpen && route !== 'world' const worldRouterState = router.state const t = (key: TranslationKey) => translate(locale, key) @@ -315,7 +314,6 @@ export function HoloLakeHome({ if (route === 'fifth-domain') { return ( navigate('eternal-lake-heart')} onEnterPufferfish={() => navigate('pufferfish')} /> @@ -326,7 +324,6 @@ export function HoloLakeHome({ return ( navigate('eternal-lake-heart')} onEnterPufferfish={() => navigate('pufferfish')} /> From 67e6fcdd388b8fbb084204e403e9fb31b6327ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Tue, 4 Aug 2026 23:11:25 +0800 Subject: [PATCH 7/7] feat: enforce Guanghu-native HoloLake runtime laws --- .../hololake-platform/.chunk/README.md | 5 +- .../hololake-platform/.chunk/config.json | 8 +- .../hololake-platform/.chunk/run-rust-gate.sh | 10 +- .../.claude/settings.local.json | 1 - .../hololake-platform/.codescene-thresholds | 2 - .../hololake-platform/.codesceneignore | 5 - product-source/hololake-platform/.codescenerc | 9 - .../hololake-platform/.github/HOOKS.md | 74 ++-- .../hololake-platform/.github/SETUP.md | 238 +---------- .../.github/hooks/pre-commit | 81 ---- .../.github/workflows/README.md | 140 +------ .../.github/workflows/ci.yml | 95 +---- product-source/hololake-platform/.gitignore | 3 - .../hololake-platform/.husky/pre-commit | 58 +-- .../hololake-platform/.husky/pre-push | 378 ++---------------- product-source/hololake-platform/AGENTS.md | 56 +-- .../hololake-platform/docs/GETTING-STARTED.md | 10 +- .../HOLOLAKE-PHASE-1-GUANGHU-WORLD-ENTRY.md | 13 +- ...herits-guanghu-native-quality-authority.md | 55 +++ ...-automatic-runtime-and-engineering-laws.md | 98 +++++ .../hololake-platform/docs/adr/README.md | 10 +- product-source/hololake-platform/package.json | 5 + .../run-hololake-native-quality-gate.sh | 157 ++++++++ .../scripts/run-vitest-coverage-shards.mjs | 33 +- .../scripts/test-guanghu-native-authority.sh | 55 +++ .../validate-guanghu-native-profile.mjs | 148 +++++++ .../site/public/landing/sponsors/SOURCES.md | 2 - .../public/landing/sponsors/codacy-dark.svg | 16 - .../public/landing/sponsors/codacy-light.svg | 16 - .../landing/sponsors/codescene-dark.svg | 3 - .../landing/sponsors/codescene-light.svg | 3 - .../site/reference/contribute.md | 2 - .../gen/apple/assets/agent-docs/all.md | 2 - .../gen/apple/assets/agent-docs/contribute.md | 2 - .../gen/apple/assets/agent-docs/reference.md | 2 - .../src-tauri/resources/agent-docs/all.md | 2 - .../agent-docs/pages/reference/contribute.md | 2 - .../resources/agent-docs/reference.md | 2 - .../src-tauri/src/guanghu_living_system.rs | 215 ++++------ .../src/assets/sponsors/codacy-dark.svg | 16 - .../src/assets/sponsors/codacy-light.svg | 16 - .../src/assets/sponsors/codescene-dark.svg | 3 - .../src/assets/sponsors/codescene-light.svg | 3 - .../src/components/FeedbackDialog.test.tsx | 22 +- .../src/components/FeedbackDialog.tsx | 18 - .../src/components/HoloLakeHome.test.tsx | 94 +++-- .../src/components/HoloLakeHome.tsx | 106 +++-- .../src/constants/feedback.ts | 2 - .../src/lib/guanghuLivingSystem.test.ts | 214 +++++++++- .../src/lib/guanghuLivingSystem.ts | 302 ++++++++++++-- .../src/utils/planGuanghuLivingSystem.test.ts | 139 +++++-- .../src/utils/planGuanghuLivingSystem.ts | 83 ++-- .../hololake-platform/standards/GLS-0844.md | 29 +- .../guanghu-native-engineering-profile.json | 157 ++++++++ .../tests/smoke/contribute-modal.spec.ts | 10 - .../hololake-platform/vite.config.ts | 6 - .../vitest.guanghu-native.config.ts | 35 ++ 57 files changed, 1766 insertions(+), 1505 deletions(-) delete mode 100644 product-source/hololake-platform/.codescene-thresholds delete mode 100644 product-source/hololake-platform/.codesceneignore delete mode 100644 product-source/hololake-platform/.codescenerc delete mode 100644 product-source/hololake-platform/.github/hooks/pre-commit create mode 100644 product-source/hololake-platform/docs/adr/0170-hololake-inherits-guanghu-native-quality-authority.md create mode 100644 product-source/hololake-platform/docs/adr/0171-guanghu-protocols-are-automatic-runtime-and-engineering-laws.md create mode 100755 product-source/hololake-platform/scripts/run-hololake-native-quality-gate.sh create mode 100755 product-source/hololake-platform/scripts/test-guanghu-native-authority.sh create mode 100644 product-source/hololake-platform/scripts/validate-guanghu-native-profile.mjs delete mode 100644 product-source/hololake-platform/site/public/landing/sponsors/codacy-dark.svg delete mode 100644 product-source/hololake-platform/site/public/landing/sponsors/codacy-light.svg delete mode 100644 product-source/hololake-platform/site/public/landing/sponsors/codescene-dark.svg delete mode 100644 product-source/hololake-platform/site/public/landing/sponsors/codescene-light.svg delete mode 100644 product-source/hololake-platform/src/assets/sponsors/codacy-dark.svg delete mode 100644 product-source/hololake-platform/src/assets/sponsors/codacy-light.svg delete mode 100644 product-source/hololake-platform/src/assets/sponsors/codescene-dark.svg delete mode 100644 product-source/hololake-platform/src/assets/sponsors/codescene-light.svg create mode 100644 product-source/hololake-platform/standards/guanghu-native-engineering-profile.json create mode 100644 product-source/hololake-platform/vitest.guanghu-native.config.ts diff --git a/product-source/hololake-platform/.chunk/README.md b/product-source/hololake-platform/.chunk/README.md index e8a84d26c..039958b0f 100644 --- a/product-source/hololake-platform/.chunk/README.md +++ b/product-source/hololake-platform/.chunk/README.md @@ -60,4 +60,7 @@ FRONTEND_COVERAGE_SHARDS=1 bash .chunk/run-sidecar-gates-local.sh false FRONTEND_COVERAGE_SHARDS=2 bash .chunk/run-sidecar-gates-local.sh false ``` -The default sidecar fast path uses two frontend coverage shards. Each shard disables per-shard coverage thresholds, then the merged V8/Istanbul coverage map is checked once against the same 70% line/function/branch/statement thresholds. +The default sidecar fast path can use two frontend coverage shards as an +observation. The merged V8/Istanbul map helps locate unexercised behavior but +does not create an acceptance threshold. Only the declared auditable core scope +inside GLS-0844 can satisfy the exact native coverage gate. diff --git a/product-source/hololake-platform/.chunk/config.json b/product-source/hololake-platform/.chunk/config.json index b381f0bbf..377cffe0a 100644 --- a/product-source/hololake-platform/.chunk/config.json +++ b/product-source/hololake-platform/.chunk/config.json @@ -25,8 +25,8 @@ "limit": 2 }, { - "name": "frontend-coverage", - "run": "pnpm test:coverage --silent", + "name": "frontend-tests", + "run": "pnpm test --silent", "role": "gate", "fileExt": ".ts,.tsx", "timeout": 600, @@ -41,8 +41,8 @@ "limit": 2 }, { - "name": "rust-coverage", - "run": "cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --ignore-filename-regex \"lib\\.rs|main\\.rs|menu\\.rs\" --fail-under-lines 85 -- --test-threads=1", + "name": "rust-tests", + "run": "cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1", "role": "gate", "fileExt": ".rs", "timeout": 900, diff --git a/product-source/hololake-platform/.chunk/run-rust-gate.sh b/product-source/hololake-platform/.chunk/run-rust-gate.sh index 619e1c21b..d26e50fb9 100644 --- a/product-source/hololake-platform/.chunk/run-rust-gate.sh +++ b/product-source/hololake-platform/.chunk/run-rust-gate.sh @@ -20,11 +20,7 @@ log_rust 'rustfmt started' cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check log_rust 'rustfmt passed' -log_rust 'coverage started' -cargo llvm-cov \ - --manifest-path src-tauri/Cargo.toml \ - --no-clean \ - --ignore-filename-regex "lib\\.rs|main\\.rs|menu\\.rs" \ - --fail-under-lines 85 \ - -- --test-threads=1 +log_rust 'tests started' +cargo test --manifest-path src-tauri/Cargo.toml -- --test-threads=1 +log_rust 'tests passed' log_rust "completed in $(($(date +%s) - start_time))s" diff --git a/product-source/hololake-platform/.claude/settings.local.json b/product-source/hololake-platform/.claude/settings.local.json index fba3ea8c5..5b6328202 100644 --- a/product-source/hololake-platform/.claude/settings.local.json +++ b/product-source/hololake-platform/.claude/settings.local.json @@ -1,7 +1,6 @@ { "permissions": { "allow": [ - "mcp__codescene__*", "Read(*)", "Bash(cat*)", "Bash(ls*)", diff --git a/product-source/hololake-platform/.codescene-thresholds b/product-source/hololake-platform/.codescene-thresholds deleted file mode 100644 index c317ae70e..000000000 --- a/product-source/hololake-platform/.codescene-thresholds +++ /dev/null @@ -1,2 +0,0 @@ -HOTSPOT_THRESHOLD=10.0 -AVERAGE_THRESHOLD=9.99 diff --git a/product-source/hololake-platform/.codesceneignore b/product-source/hololake-platform/.codesceneignore deleted file mode 100644 index dc59d4002..000000000 --- a/product-source/hololake-platform/.codesceneignore +++ /dev/null @@ -1,5 +0,0 @@ -# Exclude third-party tools and their dependencies from CodeScene analysis -tools/ -e2e/ -tests/ -scripts/ diff --git a/product-source/hololake-platform/.codescenerc b/product-source/hololake-platform/.codescenerc deleted file mode 100644 index 986748169..000000000 --- a/product-source/hololake-platform/.codescenerc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "exclude": [ - "tools/", - "scripts/", - "src-tauri/gen/", - "coverage/", - "dist/" - ] -} diff --git a/product-source/hololake-platform/.github/HOOKS.md b/product-source/hololake-platform/.github/HOOKS.md index 17c2e245e..55d2ceef4 100644 --- a/product-source/hololake-platform/.github/HOOKS.md +++ b/product-source/hololake-platform/.github/HOOKS.md @@ -1,67 +1,59 @@ # Git Hooks -This repo uses Husky hooks from `.husky/`. Those files are the source of truth. +This repository uses Husky hooks from `.husky/`. Those files are execution +surfaces; GLS-0844 (GHNQG) is the quality authority. ## Installation -`pnpm install` runs the `prepare` script and installs the hooks into `.git/hooks`. - -If you need to reinstall them manually: +`pnpm install` runs the `prepare` script and installs the hooks into +`.git/hooks`. To reinstall them, run: ```bash pnpm exec husky ``` -The hooks expect `node` and `pnpm` to be available. If they are installed via `nvm`, the hooks will try to load `~/.nvm/nvm.sh` automatically. +## Native policy -Documentation/workflow/hook-only commits and pushes are classified before Node tooling is required. Editing those files must not fail merely because `pnpm` is absent from the invoking shell. - -## Policy - -- Commit on `main` or in a detached verification worktree intended for direct promotion. +- Commit on `main` or in a detached verification worktree intended for direct + promotion. - Push `main -> origin/main` or detached `HEAD -> origin/main` only. - Never use `--no-verify`. -- `.codescene-thresholds` is a ratchet. It can only move up. -- CodeScene is additive when credentials are already configured. Missing credentials are not a Git transport error and must not prompt account creation, a trial, or a purchase. -- The Fifth Domain Router is a separate, explicitly authorized prototype-publication path. Its exact-SHA receipt proves only that an allowlisted branch reached the code channel; it does not replace `main -> main` production promotion. +- Run `bash scripts/test-guanghu-native-authority.sh` before repository checks. +- Accept only `GHNQG_PASS_100` bound to the exact commit and tree. +- Treat any incomplete required gate as `GHNQG_FAIL_0`. +- Do not use minimum percentages, weighted scores, waivers, or external + analyzers as acceptance states. +- The Fifth Domain Router is a separate, explicitly authorized + prototype-publication path. Its exact-SHA receipt proves repository + publication only; it is not a merge, release, deployment, runtime-health, or + persona-birth receipt. ## Pre-commit -`.husky/pre-commit` blocks commits unless all applicable checks are true: +`.husky/pre-commit` keeps the edit loop fast: -- staged TypeScript files pass `pnpm lint --quiet` -- documentation/workflow/hook-only commits are identified without running application checks +- staged TypeScript files pass repository lint; +- documentation, workflow, and hook-only commits are classified without + requiring application tooling. -If `CODESCENE_PAT` or `CODESCENE_PROJECT_ID` is missing, the CodeScene portion is skipped, but the rest of the hook still runs. Record CodeScene as `not_run_unconfigured`; do not treat the skip as a failed commit. +Passing pre-commit is evidence, not a publication authorization. ## Pre-push -`.husky/pre-push` blocks pushes unless all of the following are true: +`.husky/pre-push` requires: -- the current state is `main` or detached `HEAD` -- every pushed branch ref is `refs/heads/main -> refs/heads/main` or detached `HEAD -> refs/heads/main` -- TypeScript and the Vite build pass -- frontend coverage passes -- Rust lint and Rust coverage pass when `src-tauri/` changed -- the curated Playwright core smoke lane passes via `pnpm playwright:smoke` -- current CodeScene Hotspot and Average health are both at or above `.codescene-thresholds` +- native-authority contract validation; +- TypeScript and Vite build; +- frontend tests; +- Rust lint, format, and tests when Rust source changed; +- the curated Playwright core smoke lane; +- a GLS-0844 receipt from the repository-owned executor. -If the remote CodeScene scores are better than the current thresholds, the hook updates `.codescene-thresholds`, stages it, and stops the push. Commit that file normally, then push again. The hook does not auto-commit or bypass itself. - -If CodeScene credentials are missing, the hook prints a warning and continues after all repository-owned checks pass. This is the intended no-subscription behavior. - -## Legacy Files - -The legacy `pre-commit` file under `.github/hooks/` is archival only. Do not copy it into `.git/hooks`; use Husky and `.husky/` instead. The old design `post-commit` auto-implementation hook was removed because it depended on obsolete one-off scripts. `install-hooks.sh` remains as a reinstall helper that runs Husky. +The executor writes its receipt outside the source repository. On BingShuo's +workstation, receipts go to JZAO when that volume is mounted. ## Troubleshooting -If a hook cannot find `node` or `pnpm`: - -```bash -export NVM_DIR="$HOME/.nvm" -. "$NVM_DIR/nvm.sh" -nvm use node -``` - -Then retry the commit or push. +If a hook cannot find `node` or `pnpm`, load the configured Node environment +and retry. Missing tooling makes the applicable gate incomplete; it does not +create a third acceptance state. diff --git a/product-source/hololake-platform/.github/SETUP.md b/product-source/hololake-platform/.github/SETUP.md index d56b05597..afb5c727b 100644 --- a/product-source/hololake-platform/.github/SETUP.md +++ b/product-source/hololake-platform/.github/SETUP.md @@ -1,227 +1,21 @@ -# CI/CD Setup Guide +# HoloLake 代码频道设置 -## Quick Start +HoloLake 不需要外部评分平台、外部质量令牌或外部项目编号才能开发、提交和验证。 -### 1. Add GitHub Secrets +现行工程权威来自第五域代码频道 `REPO-012` 的注册协议,以及本仓对这些协议的 +产品层绑定: -Nel repository GitHub (Settings → Secrets and variables → Actions → New repository secret): +- 远端协议注册表:`GLS-PROTOCOL-REGISTRY-20260731` +- 产品工程档案:`standards/guanghu-native-engineering-profile.json` +- 原生权威检查:`pnpm test:native-authority` +- 产品测试:`pnpm test` +- 静态检查:`pnpm lint` +- 构建验证:`pnpm build` -**CODESCENE_TOKEN** -``` - -``` +所有必需门必须绑定同一份源码树并全部完成,才允许形成 `GHNQG_PASS_100`。 +任一必需门失败、缺失或没有证据,本轮状态就是 `GHNQG_FAIL_0`。测试框架、 +编译器、代码托管和流水线只是执行这些门的施工条件,不拥有发布、部署或真实 +状态的裁决权。 -**CODESCENE_PROJECT_ID** -Trova l'ID del progetto nella dashboard CodeScene (URL: `https://codescene.io/projects//...`) - -**VITE_SENTRY_DSN** -``` - -``` - -**SENTRY_DSN** -``` - -``` - -**VITE_POSTHOG_KEY** -``` - -``` - -**VITE_POSTHOG_HOST** -``` -https://eu.i.posthog.com -``` - -**Windows Authenticode release signing** - -Windows release artifacts can be Authenticode-signed when a trusted code-signing certificate is available. Until Windows certificate provisioning is complete, the release workflow warns and publishes Windows artifacts with Tauri updater signatures only. - -To enable Authenticode, configure a trusted certificate exported as base64 PFX data: - -``` -WINDOWS_CODE_SIGNING_CERTIFICATE= -WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD= -``` - -Optional: - -``` -WINDOWS_CODE_SIGNING_CERTIFICATE_THUMBPRINT= -WINDOWS_CODE_SIGNING_TIMESTAMP_URL=https://timestamp.digicert.com -``` - -Legacy aliases `WINDOWS_CERTIFICATE`, `WINDOWS_CERTIFICATE_PASSWORD`, `WINDOWS_CERTIFICATE_THUMBPRINT`, and `WINDOWS_TIMESTAMP_URL` are still accepted by the signing script. Do not use a self-signed certificate for public releases; Windows Authenticode release signing needs a certificate from a trusted CA or signing service. - -### 2. Enable GitHub Actions - -- Vai su Settings → Actions → General -- Assicurati che "Allow all actions and reusable workflows" sia selezionato - -### 3. Configure Branch Protection (Optional ma Raccomandato) - -Settings → Branches → Add branch protection rule: - -**Branch name pattern**: `main` - -Abilita: -- ✅ Require status checks to pass before merging - - Select: `Tests & Quality Checks` -- ✅ Require branches to be up to date before merging -- ✅ Do not allow bypassing the above settings - -Questo forza tutti i check a passare prima di poter fare merge su main. - -### 4. Test Locally Prima di Pushare - -```bash -# Full test suite -pnpm test && cargo test --manifest-path=src-tauri/Cargo.toml - -# Coverage -pnpm test:coverage - -# Lint -pnpm lint -cargo clippy --manifest-path=src-tauri/Cargo.toml - -# Format check -cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check -``` - -## What Gets Checked - -### ✅ Tests -- Frontend: Vitest -- Backend: `cargo test` - -### 📊 Coverage -- Threshold: 70% (lines, functions, branches, statements) -- Configurabile in `vite.config.ts` - -### 🏥 Code Health -- CodeScene delta analysis -- **Fail se code health diminuisce** -- Confronta HEAD vs base branch - -### 📡 Telemetry In Release Builds -- `release.yml` e `release-stable.yml` devono ricevere `VITE_SENTRY_DSN`, `SENTRY_DSN`, `VITE_POSTHOG_KEY`, `VITE_POSTHOG_HOST` -- `VITE_SENTRY_DSN` inizializza il frontend Sentry bundle -- `SENTRY_DSN` inizializza Sentry nel binary Rust/Tauri -- `VITE_POSTHOG_KEY` / `VITE_POSTHOG_HOST` permettono ai build distribuiti di inizializzare PostHog quando l'utente abilita analytics - -### 📝 Documentation -- **Warning se modifichi `src/` o `src-tauri/` ma non aggiorni `docs/`** -- Non blocca il merge, solo un reminder -- Skip il check con `[skip docs]` nel commit message -- Aggiorna docs solo se la modifica invalida qualcosa già documentato - -### 🎨 Lint & Format -- ESLint per frontend -- Clippy + rustfmt per Rust - -## Workflow File - -Il workflow è in `.github/workflows/ci.yml`. - -**Trigger**: -- Push su `main` o `experiment/*` -- Pull request verso `main` - -**Runner**: `macos-latest` (necessario per Tauri + Rust) - -## Customization - -### Soglie Coverage - -Modifica `vite.config.ts`: - -```typescript -coverage: { - thresholds: { - lines: 80, // Aumenta se vuoi più coverage - functions: 80, - branches: 80, - statements: 80, - } -} -``` - -### Documentation Check - -Il check **avvisa** (non fallisce) se: -1. Modifichi file in `src/` o `src-tauri/` -2. NON modifichi nulla in `docs/` - -**Quando aggiornare docs:** -- Cambi architettura → aggiorna `docs/ARCHITECTURE.md` -- Cambi astrazioni chiave → aggiorna `docs/ABSTRACTIONS.md` -- Cambi theme system → aggiorna `docs/THEMING.md` -- Bug fix / refactor interno → `[skip docs]` nel commit message - -**Skip il check:** -```bash -git commit -m "fix: editor scroll bug [skip docs]" -``` - -### CodeScene Fail Threshold - -Nel workflow, modifica: - -```yaml -- name: CodeScene Delta Analysis - uses: codescene-oss/codescene-delta-analysis-action@v1 - with: - fail-on-declining-code-health: true # Cambia a false per warning-only - minimum-code-health-score: 8.0 # Aggiungi per soglia assoluta -``` - -## Troubleshooting - -### CodeScene fails con "Project not found" -- Verifica che `CODESCENE_PROJECT_ID` sia corretto -- Controlla che il token abbia accesso al progetto - -### Coverage check fails -- Verifica che `@vitest/coverage-v8` sia installato: `pnpm add -D @vitest/coverage-v8` -- Le soglie sono configurabili in `vite.config.ts` - -### Docs check avvisa anche se non serve aggiornare docs -- È solo un warning, non blocca -- Skip con `[skip docs]` nel commit message -- Oppure ignora — è un reminder, non un requisito - -### Workflow non si attiva -- Verifica che il file sia in `.github/workflows/ci.yml` -- Controlla che GitHub Actions sia abilitato nelle settings -- Il workflow parte solo su push/PR verso `main` o branch `experiment/*` - -## Example CI Pass - -``` -✅ Run frontend tests -✅ Run Rust tests -✅ Run frontend coverage (75% lines, 73% functions) -✅ CodeScene Delta Analysis (code health: 9.2 → 9.3) -✅ Check docs are updated (docs/ARCHITECTURE.md modified) -✅ Lint frontend -✅ Clippy (Rust) -✅ Format check (Rust) -``` - -## Example CI Warning - -``` -⚠️ Code files changed but docs/ not updated - Changed code files: - - src/components/Editor.tsx - - src-tauri/src/vault.rs - - If this change affects architecture/abstractions/design documented in docs/, - please update the relevant documentation files. - - To skip this check, include [skip docs] in your commit message. -``` - -Questo è solo un reminder. Se la modifica non invalida la documentazione esistente, puoi ignorarlo o usare `[skip docs]`. +机密信息只通过设备安全存储和获准的运行能力使用,不写入仓库、构建产物或 +同步数据。 diff --git a/product-source/hololake-platform/.github/hooks/pre-commit b/product-source/hololake-platform/.github/hooks/pre-commit deleted file mode 100644 index 94e6aedb0..000000000 --- a/product-source/hololake-platform/.github/hooks/pre-commit +++ /dev/null @@ -1,81 +0,0 @@ -#!/bin/bash -# Pre-commit hook: CodeScene Code Health Check -# Copy to .git/hooks/pre-commit and make executable - -set -e - -# Allow bypass with --no-verify or [skip codescene] in commit message -if git log -1 --pretty=%B 2>/dev/null | grep -qi '\[skip codescene\]'; then - echo "⏭️ CodeScene check skipped (commit message contains [skip codescene])" - exit 0 -fi - -echo "🔍 Running CodeScene Code Health check..." - -# Check if we have staged files to analyze -STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|rs)$' || true) - -if [ -z "$STAGED_FILES" ]; then - echo "✅ No TypeScript/Rust files staged, skipping CodeScene check" - exit 0 -fi - -# Get current branch -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -# Determine base branch for comparison -if [ "$CURRENT_BRANCH" = "main" ]; then - BASE_REF="HEAD~1" -else - BASE_REF="origin/main" -fi - -echo " Comparing against: $BASE_REF" - -# Check if we have CodeScene configured (MCP or CLI) -CODESCENE_MCP_CONFIG="$HOME/.claude/mcp.json" -CODESCENE_TOKEN_FILE="$HOME/.codescene/token" - -if [ ! -f "$CODESCENE_MCP_CONFIG" ] && [ ! -f "$CODESCENE_TOKEN_FILE" ]; then - echo "⚠️ CodeScene not configured" - echo " Install CodeScene MCP (configured in ~/.claude/mcp.json)" - echo " Or place token at ~/.codescene/token" - echo " Proceeding without check (use 'git commit --no-verify' to skip this warning)" - exit 0 -fi - -# Simple health check using git diff stats -echo " Analyzing code changes..." - -# Get file changes -LINES_ADDED=$(git diff --cached --numstat | awk '{sum+=$1} END {print sum}') -LINES_REMOVED=$(git diff --cached --numstat | awk '{sum+=$2} END {print sum}') - -# Check for large files (potential complexity) -LARGE_FILES=$(git diff --cached --numstat | awk '$1 > 500 || $2 > 500 {print $3}') - -if [ ! -z "$LARGE_FILES" ]; then - echo "⚠️ Large file changes detected (>500 lines):" - echo "$LARGE_FILES" | sed 's/^/ - /' - echo "" - echo " Consider:" - echo " - Breaking into smaller commits" - echo " - Reviewing with Claude Code + CodeScene MCP" - echo " - Running: claude 'Review code health of staged changes'" - echo "" - read -p " Continue anyway? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "❌ Commit aborted" - exit 1 - fi -fi - -echo "✅ CodeScene check passed" -echo " +$LINES_ADDED -$LINES_REMOVED lines" -echo "" -echo " 💡 For detailed code health analysis, run:" -echo " claude 'Check code health of this commit with CodeScene MCP'" -echo "" - -exit 0 diff --git a/product-source/hololake-platform/.github/workflows/README.md b/product-source/hololake-platform/.github/workflows/README.md index 250e5e023..aa56edc50 100644 --- a/product-source/hololake-platform/.github/workflows/README.md +++ b/product-source/hololake-platform/.github/workflows/README.md @@ -1,133 +1,15 @@ -# CI/CD Setup +# HoloLake 原生工程流水线 -## GitHub Actions Workflow +流水线执行 HoloLake 已登记的光湖原生工程门,不调用外部评分服务,也不把 +托管平台的状态当作光湖事实。 -Il workflow `ci.yml` esegue i seguenti check automatici: +## 当前执行顺序 -### 1. Tests -- Frontend: `pnpm test` -- Rust backend: `cargo test` +1. 回读产品工程档案和固定的 `REPO-012` 协议注册表提交。 +2. 验证第三方评分规则没有重新进入现行权威面。 +3. 验证人格系统输入、类型化输出、能力登记和证据回执合同。 +4. 运行产品单元、集成、格式、静态检查、脚本语法和构建门。 +5. 对精确提交与源码树汇总 `GHNQG_PASS_100` 或 `GHNQG_FAIL_0`。 -### 2. Test Coverage -- Frontend: vitest con coverage reporting -- Upload automatico su Codecov dai report LCOV frontend + Rust -- Threshold configurabile in `vitest.config.ts` - -### 3. Code Health (CodeScene) -- Delta analysis su ogni PR/push -- Fail se il code health diminuisce -- Richiede secrets configurati (vedi sotto) - -### 4. Documentation Check -- Verifica che se cambia codice in `src/` o `src-tauri/`, anche `docs/` viene aggiornato -- **Warning only** — non blocca il merge, solo un reminder -- Skip con `[skip docs]` nel commit message -- Aggiorna docs solo se la modifica invalida architettura/astrazioni/design già documentati - -### 5. Lint & Format -- ESLint per frontend -- Clippy + rustfmt per Rust - -## Setup Required - -### CodeScene Secrets -Aggiungi questi secrets nel repository GitHub (Settings → Secrets → Actions): - -``` -CODESCENE_TOKEN= -CODESCENE_PROJECT_ID= -``` - -Il PAT di CodeScene è lo stesso che usi localmente (~/.codescene/token). -Il project ID lo trovi nella dashboard CodeScene. - -### Codecov Setup -- Installa/attiva il repo in Codecov una volta sola tramite GitHub App / import del repository. -- Nessun `CODECOV_TOKEN` richiesto in GitHub Actions: `ci.yml` usa OIDC (`id-token: write` + `use_oidc: true`). -- Il workflow carica `coverage/lcov.info` (Vitest) e `coverage/rust.lcov` (cargo-llvm-cov). -- L'action Codecov resta con integrity validation attiva. Se Codecov ruota la chiave GPG del CLI, aggiorna il pin dell'action invece di usare `skip_validation`. - -### Telemetry Secrets For Release Builds -Aggiungi anche questi secrets per i workflow `release.yml` e `release-stable.yml`: - -``` -VITE_SENTRY_DSN= -SENTRY_DSN= -VITE_POSTHOG_KEY= -VITE_POSTHOG_HOST=https://eu.i.posthog.com -``` - -Senza questi valori, i build distribuiti possono mantenere i toggle telemetry nelle Settings ma non inizializzare davvero PostHog/Sentry. - -### Windows Authenticode Secrets For Release Builds -Windows alpha e stable release builds usano sempre le firme Tauri updater. Se i secret Authenticode sono presenti, il workflow firma anche gli installer Windows e verifica le firme; se mancano, emette un warning e pubblica gli artifact Windows senza Authenticode finche' il certificato non e' pronto. - -``` -WINDOWS_CODE_SIGNING_CERTIFICATE= -WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD= -``` - -Opzionale: - -``` -WINDOWS_CODE_SIGNING_CERTIFICATE_THUMBPRINT= -WINDOWS_CODE_SIGNING_TIMESTAMP_URL=https://timestamp.digicert.com -``` - -Il certificato deve essere un certificato di code signing trusted; un certificato self-signed non e' adatto per i release artifact pubblici. - -### Coverage Thresholds -Configura in `vitest.config.ts`: - -```typescript -export default defineConfig({ - test: { - coverage: { - lines: 80, - functions: 80, - branches: 80, - statements: 80, - // Fail CI se sotto threshold - thresholds: { - lines: 80, - functions: 80, - branches: 80, - statements: 80 - } - } - } -}) -``` - -## Local Testing - -Prima di pushare, puoi testare localmente: - -```bash -# Run all tests -pnpm test && cargo test - -# Check coverage -pnpm test:coverage - -# Lint -pnpm lint -cargo clippy -cargo fmt --check - -# CodeScene (local) -codescene delta-analysis --base-revision origin/main -``` - -## Workflow Triggers - -- **Push**: su `main` -- **Pull Request**: verso `main` -- **Manuale**: `workflow_dispatch` - -Nota: l'upload a Codecov gira su push a `main` e sulle PR dello stesso repository. Le PR da fork saltano l'upload per evitare problemi di permessi OIDC. - -## Status Checks - -Tutti i check devono passare prima di poter fare merge. -Se un check fallisce, vedrai il dettaglio nei logs di GitHub Actions. +流水线通过只证明该源码树完成了已声明的工程门,不证明已经上传、部署、在线、 +出生或取得服务器权限。部署与运行状态必须由目标节点自己的回执独立证明。 diff --git a/product-source/hololake-platform/.github/workflows/ci.yml b/product-source/hololake-platform/.github/workflows/ci.yml index 09af7902d..daa72b9e9 100644 --- a/product-source/hololake-platform/.github/workflows/ci.yml +++ b/product-source/hololake-platform/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 # Full history for CodeScene + fetch-depth: 0 - name: Setup pnpm uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa @@ -72,43 +72,8 @@ jobs: if: steps.docs-changes.outputs.should-build == 'true' run: pnpm docs:build - # ── 1. Code Health (CodeScene — Hotspot + Average Code Health gates) ── - # Enforces minimum floors on BOTH hotspot and average code health. - # Thresholds come from .codescene-thresholds so CI and local hooks match. - - name: Code Health gates - env: - CODESCENE_PAT: ${{ secrets.CODESCENE_PAT }} - CODESCENE_PROJECT_ID: ${{ secrets.CODESCENE_PROJECT_ID }} - run: | - HOTSPOT_THRESHOLD=$(grep '^HOTSPOT_THRESHOLD=' .codescene-thresholds | cut -d= -f2) - AVERAGE_THRESHOLD=$(grep '^AVERAGE_THRESHOLD=' .codescene-thresholds | cut -d= -f2) - API_RESPONSE=$(curl -sf \ - -H "Authorization: Bearer $CODESCENE_PAT" \ - -H "Accept: application/json" \ - "https://api.codescene.io/v2/projects/$CODESCENE_PROJECT_ID") - HOTSPOT_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['hotspot_code_health']['now'])") - AVERAGE_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['code_health']['now'])") - echo "Hotspot Code Health: $HOTSPOT_SCORE (threshold: $HOTSPOT_THRESHOLD)" - echo "Average Code Health: $AVERAGE_SCORE (threshold: $AVERAGE_THRESHOLD)" - python3 -c " - hotspot = float('$HOTSPOT_SCORE') - average = float('$AVERAGE_SCORE') - ht = float('$HOTSPOT_THRESHOLD') - at = float('$AVERAGE_THRESHOLD') - failed = False - if hotspot < ht: - print(f'❌ Hotspot Code Health {hotspot:.2f} is below threshold {ht}') - failed = True - else: - print(f'✅ Hotspot Code Health {hotspot:.2f} ≥ {ht}') - if average < at: - print(f'❌ Average Code Health {average:.2f} is below threshold {at}') - failed = True - else: - print(f'✅ Average Code Health {average:.2f} ≥ {at}') - if failed: - exit(1) - " + - name: Guanghu native authority contract + run: pnpm test:native-authority && pnpm test:native-core # ── 2. Documentation check (warning only — does not fail build) ─────── - name: Check docs are updated @@ -133,7 +98,7 @@ jobs: run: pnpm lint frontend-tests: - name: Frontend Tests & Coverage + name: Frontend Tests runs-on: macos-15 steps: @@ -153,25 +118,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - # The coverage command runs the canonical frontend test suite. - name: Bundle MCP server resources (required by Tauri build) run: node scripts/bundle-mcp-server.mjs - - name: Frontend tests + coverage (≥70% lines/functions/branches/statements) - run: pnpm test:coverage - # Thresholds configured in vite.config.ts — exits non-zero if coverage drops - - - name: Upload frontend coverage to Codecov - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - uses: codecov/codecov-action@5975040f7f7d40edaff8d784b576fd65ae95c073 - with: - use_oidc: true - fail_ci_if_error: true - disable_search: true - files: ./coverage/lcov.info - flags: frontend - verbose: true - # OIDC avoids long-lived CODECOV_TOKEN secrets. + - name: Frontend tests + run: pnpm test rust-quality: name: Rust Tests & Quality Checks @@ -196,32 +147,11 @@ jobs: restore-keys: | ${{ runner.os }}-cargo-${{ env.RUST_TARGET_CACHE_VERSION }}- - - name: Install cargo-llvm-cov + - name: Install native coverage executor uses: taiki-e/install-action@e5de28abeb52d916c5e5875d54b21a9e738b61ec - - name: Rust tests + coverage (≥85% lines) - run: | - mkdir -p coverage - cargo llvm-cov \ - --manifest-path src-tauri/Cargo.toml \ - --ignore-filename-regex 'lib\.rs|main\.rs|menu\.rs' \ - --lcov \ - --output-path coverage/rust.lcov \ - --fail-under-lines 85 - # cargo-llvm-cov exits non-zero if line coverage drops below 85% - # lib.rs/main.rs/menu.rs are Tauri boilerplate -- not meaningfully unit-testable. - - - name: Upload Rust coverage to Codecov - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - uses: codecov/codecov-action@5975040f7f7d40edaff8d784b576fd65ae95c073 - with: - use_oidc: true - fail_ci_if_error: true - disable_search: true - files: ./coverage/rust.lcov - flags: rust - verbose: true - # OIDC avoids long-lived CODECOV_TOKEN secrets. + - name: Rust tests + run: cargo test --manifest-path src-tauri/Cargo.toml - name: Clippy (Rust) run: cargo clippy --manifest-path=src-tauri/Cargo.toml -- -D warnings @@ -229,6 +159,13 @@ jobs: - name: Format check (Rust) run: cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check + - name: GLS-0844 HoloLake native quality receipt + run: | + mkdir -p "$RUNNER_TEMP/ghnqg" + bash scripts/run-hololake-native-quality-gate.sh \ + "$RUNNER_TEMP/ghnqg/GHNQG-${GITHUB_SHA}.hldp" + grep -Fxq 'result: PASS_100' "$RUNNER_TEMP/ghnqg/GHNQG-${GITHUB_SHA}.hldp" + linux-build: name: Linux build verification # Keep the normal push CI lane under the 10-minute target. The release diff --git a/product-source/hololake-platform/.gitignore b/product-source/hololake-platform/.gitignore index a89e22a01..bd0d42c8a 100644 --- a/product-source/hololake-platform/.gitignore +++ b/product-source/hololake-platform/.gitignore @@ -76,6 +76,3 @@ CODE-HEALTH-REPORT.md .env .env.local .env.*.local - -# Local Codacy CLI runtime/config generated by the MCP server -.codacy/ diff --git a/product-source/hololake-platform/.husky/pre-commit b/product-source/hololake-platform/.husky/pre-commit index c3bf5797c..1d9bd0483 100755 --- a/product-source/hololake-platform/.husky/pre-commit +++ b/product-source/hololake-platform/.husky/pre-commit @@ -1,55 +1,5 @@ -#!/bin/sh -# Pre-commit: fast local lint gate before commit. Full suite runs in pre-push/CI. -set -e +#!/usr/bin/env sh +set -eu -ensure_node_tooling() { - if command -v node >/dev/null 2>&1 && command -v pnpm >/dev/null 2>&1; then - return 0 - fi - - NVM_DIR="${NVM_DIR:-$HOME/.nvm}" - if [ -s "$NVM_DIR/nvm.sh" ]; then - # shellcheck disable=SC1090 - . "$NVM_DIR/nvm.sh" --no-use - nvm use --silent node >/dev/null 2>&1 || true - fi - - if ! command -v node >/dev/null 2>&1 || ! command -v pnpm >/dev/null 2>&1; then - echo "❌ node and pnpm must be available before committing" - echo " Install them or make sure your nvm setup is available to git hooks." - exit 1 - fi -} - -ensure_node_tooling - -echo "🔍 Pre-commit checks..." - -STAGED_FILES=$(git diff --cached --name-only) -APP_CHANGED=false - -for FILE in $STAGED_FILES; do - case "$FILE" in - .github/workflows/*|.husky/*|docs/*|*.md) - ;; - *) - APP_CHANGED=true - ;; - esac -done - -if [ "$APP_CHANGED" = false ]; then - echo " → app checks skipped (docs/workflow/hooks only)" - echo "✅ Pre-commit passed" - exit 0 -fi - -# Lint only when frontend source files are staged. Typecheck and test coverage -# run in the pre-push gate. -STAGED_LINTABLE=$(echo "$STAGED_FILES" | grep -E '\.(ts|tsx|js|jsx|mjs)$' || true) -if [ -n "$STAGED_LINTABLE" ]; then - echo " → lint..." - pnpm lint --quiet -fi - -echo "✅ Pre-commit passed" +bash scripts/test-guanghu-native-authority.sh +node scripts/validate-guanghu-native-profile.mjs diff --git a/product-source/hololake-platform/.husky/pre-push b/product-source/hololake-platform/.husky/pre-push index 21d14aa22..6cf79a4e3 100755 --- a/product-source/hololake-platform/.husky/pre-push +++ b/product-source/hololake-platform/.husky/pre-push @@ -1,355 +1,41 @@ -#!/bin/sh -# Pre-push: full CI checks run locally before any push. -# This replaces remote CI for normal task pushes. -# DO NOT skip with --no-verify (Claude Code is configured to never do this). -# -# ── Optimizations (Feb 2026) ───────────────────────────────────────────── -# -# 1. --no-clean on cargo llvm-cov: reuses previous instrumented build -# artifacts for incremental compilation. Reduces Rust coverage from -# ~8 min (full recompile) to ~30-60s (incremental rebuild). -# -# 2. Change detection: skips Rust checks entirely when no files under -# src-tauri/ changed. Saves ~1-2 min on frontend-only pushes. -# -# 3. Merged redundant test runs: frontend coverage (step 2) already runs -# all tests, so the separate test step was removed. -# -# 4. Fast-fail ordering: within Rust checks, fast lints (fmt ~2s, clippy -# ~15s) run before slow coverage (~30-60s) for quicker feedback. -# -# 5. Force full coverage: set LAPUTA_FULL_COVERAGE=1 to run cargo llvm-cov -# without --no-clean (clean rebuild, accurate baseline). -# -# Expected times (warm cache, incremental): -# Frontend only: ~1 min -# Frontend+Rust: ~2-3 min -# Full coverage: ~9-10 min (with LAPUTA_FULL_COVERAGE=1) -# ───────────────────────────────────────────────────────────────────────── -set -e - -ensure_cargo_tooling() { - if command -v cargo >/dev/null 2>&1; then - return 0 - fi - - if [ -s "$HOME/.cargo/env" ]; then - # shellcheck disable=SC1091 - . "$HOME/.cargo/env" - fi - - if ! command -v cargo >/dev/null 2>&1; then - echo "❌ cargo must be available before pushing" - echo " Install Rust via https://rustup.rs or ensure ~/.cargo/bin is in PATH." - exit 1 - fi -} - -ensure_node_tooling() { - if command -v node >/dev/null 2>&1 && command -v pnpm >/dev/null 2>&1; then - return 0 - fi - - NVM_DIR="${NVM_DIR:-$HOME/.nvm}" - if [ -s "$NVM_DIR/nvm.sh" ]; then - # shellcheck disable=SC1090 - . "$NVM_DIR/nvm.sh" --no-use - nvm use --silent node >/dev/null 2>&1 || true - fi - - if ! command -v node >/dev/null 2>&1 || ! command -v pnpm >/dev/null 2>&1; then - echo "❌ node and pnpm must be available before pushing" - echo " Install them or make sure your nvm setup is available to git hooks." - exit 1 - fi -} - -require_main_push() { - CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - if [ "$CURRENT_BRANCH" != "main" ] && [ "$CURRENT_BRANCH" != "HEAD" ]; then - echo "❌ Pushes must happen from main or a detached HEAD that is pushed directly to main. Current branch: $CURRENT_BRANCH" - exit 1 - fi - - while IFS=' ' read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do - [ -z "$LOCAL_REF" ] && continue - - case "$LOCAL_REF:$REMOTE_REF" in - refs/heads/main:refs/heads/main) - ;; - HEAD:refs/heads/main) - ;; - refs/tags/*:refs/tags/*) - ;; - *) - echo "❌ Pushes must be main -> main only." - echo " Attempted: ${LOCAL_REF:-} -> ${REMOTE_REF:-}" - exit 1 - ;; - esac - done <&2 + exit 1 +fi -echo "" -echo "🚀 Pre-push checks (replaces CI — do not skip)" -echo "================================================" - -# ── Detect what changed ───────────────────────────────────────────────── -PUSH_TARGET=$(git rev-parse @{push} 2>/dev/null || echo "") -if [ -z "$PUSH_TARGET" ]; then - while IFS=' ' read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do - [ -z "$LOCAL_REF" ] && continue - case "$LOCAL_REF:$REMOTE_REF:$REMOTE_SHA" in - HEAD:refs/heads/main:0000000000000000000000000000000000000000) - ;; - HEAD:refs/heads/main:*) - PUSH_TARGET="$REMOTE_SHA" - break - ;; - esac - done <&2 + exit 1 + ;; + esac +done </dev/null 2>&1; then - echo "☁️ bash not found; falling back to local automatic checks" - return 1 - fi - - echo "" - echo "☁️ Running automatic checks on Chunk sidecar..." - if bash .chunk/run-sidecar-gates-local.sh "$RUST_CHANGED"; then - echo " ✅ Chunk sidecar automatic checks OK" - return 0 - else - SIDECAR_STATUS=$? - fi - - if [ "$SIDECAR_STATUS" -eq 86 ]; then - echo " ⚠️ Chunk sidecar unavailable; falling back to local automatic checks" - return 1 - fi - - echo " ❌ Chunk sidecar automatic checks FAILED" - exit "$SIDECAR_STATUS" -} - -if ! run_sidecar_automatic_checks; then - ensure_cargo_tooling - - # ── 0. Frontend lint ─────────────────────────────────────────────────── - echo "" - echo "🔎 [0/6] Frontend lint..." - pnpm lint - echo " ✅ Lint OK" - - # ── 1. TypeScript + Vite build ────────────────────────────────────────── - echo "" - echo "📦 [1/6] TypeScript + Vite build..." - pnpm build - echo " ✅ Build OK" - - # ── 2. Frontend coverage (≥70%) — includes all unit tests ─────────────── - echo "" - echo "📊 [2/6] Frontend tests + coverage (≥70%)..." - FRONTEND_COVERAGE_CONCURRENCY="${FRONTEND_COVERAGE_CONCURRENCY:-1}" \ - node scripts/run-vitest-coverage-shards.mjs --silent - echo " ✅ Frontend coverage OK" - - # ── 3. Rust lint (clippy + fmt) — fast, run before coverage ───────────── - echo "" - if [ "$RUST_CHANGED" = true ]; then - echo "🔧 [3/6] Clippy + rustfmt..." - cargo clippy --manifest-path=src-tauri/Cargo.toml -- -D warnings - cargo fmt --manifest-path=src-tauri/Cargo.toml -- --check - echo " ✅ Rust lint OK" - else - echo "⏭️ [3/6] Rust lint — skipped (no src-tauri/ changes)" - fi - - # ── 4. Rust coverage (≥85% lines) ────────────────────────────────────── - echo "" - if [ "$RUST_CHANGED" = true ]; then - LLVM_COV_FLAGS="--no-clean" - if [ "${LAPUTA_FULL_COVERAGE:-0}" = "1" ]; then - LLVM_COV_FLAGS="" - echo "🦀 [4/6] Rust coverage — FULL (LAPUTA_FULL_COVERAGE=1)..." - else - echo "🦀 [4/6] Rust coverage (≥85%, incremental)..." - fi - # Unset GIT_DIR so git tests create isolated repos without inheriting hook context - unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE - # shellcheck disable=SC2086 - cargo llvm-cov \ - --manifest-path src-tauri/Cargo.toml \ - $LLVM_COV_FLAGS \ - --ignore-filename-regex "lib\.rs|main\.rs|menu\.rs" \ - --fail-under-lines 85 \ - -- --test-threads=1 - echo " ✅ Rust coverage OK" - else - echo "⏭️ [4/6] Rust coverage — skipped (no src-tauri/ changes)" - fi - - # ── 5. Playwright core smoke lane (if any exist) ────────────────────── - echo "" - SMOKE_FILES=$(find tests/smoke tests/integration -name '*.spec.ts' 2>/dev/null | head -1) - if [ -n "$SMOKE_FILES" ]; then - echo "🎭 [5/6] Playwright core smoke tests..." - if ! pnpm playwright:smoke; then - echo " ❌ Core smoke tests FAILED" - exit 1 - fi - echo " ✅ Core smoke tests OK" - else - echo "⏭️ [5/6] Playwright core smoke tests — skipped (no tests/**/*.spec.ts)" - fi -fi - -# ── 6. CodeScene code health gate (ratchet) ────────────────────────────── -# Thresholds live in .codescene-thresholds and only ever go UP (ratchet). -# If remote scores improved, the hook updates the file and stops so the new -# floor is committed with normal verified hooks before the next push. -# If the remote baseline is already below threshold, allow recovery pushes to -# land; otherwise the stale remote score would block the refactors required to -# restore the gate. -THRESHOLDS_FILE="$(git rev-parse --show-toplevel)/.codescene-thresholds" -HOTSPOT_MIN=9.45 -AVERAGE_MIN=9.29 -if [ -f "$THRESHOLDS_FILE" ]; then - HOTSPOT_MIN=$(grep HOTSPOT_THRESHOLD "$THRESHOLDS_FILE" | cut -d= -f2) - AVERAGE_MIN=$(grep AVERAGE_THRESHOLD "$THRESHOLDS_FILE" | cut -d= -f2) -fi - -echo "" -echo "🏥 [6/6] CodeScene code health (Hotspot ≥${HOTSPOT_MIN} + Average ≥${AVERAGE_MIN})..." -if [ -z "$CODESCENE_PAT" ] || [ -z "$CODESCENE_PROJECT_ID" ]; then - echo " ⚠️ CODESCENE_PAT or CODESCENE_PROJECT_ID not set — skipping" -else - API_RESPONSE=$(curl -sf \ - -H "Authorization: Bearer $CODESCENE_PAT" \ - -H "Accept: application/json" \ - "https://api.codescene.io/v2/projects/$CODESCENE_PROJECT_ID" 2>/dev/null || echo "{}") - HOTSPOT_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['hotspot_code_health']['now'])" 2>/dev/null || echo "") - AVERAGE_SCORE=$(echo "$API_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['analysis']['code_health']['now'])" 2>/dev/null || echo "") - if [ -z "$HOTSPOT_SCORE" ] || [ -z "$AVERAGE_SCORE" ]; then - echo " ⚠️ Could not fetch remote scores — skipping (CI will enforce)" - else - echo " Remote Hotspot Code Health: $HOTSPOT_SCORE (threshold: $HOTSPOT_MIN)" - echo " Remote Average Code Health: $AVERAGE_SCORE (threshold: $AVERAGE_MIN)" - PYTHON_STATUS=0 - python3 -c " -import sys - -hotspot = float('$HOTSPOT_SCORE') -average = float('$AVERAGE_SCORE') -hotspot_min = float('$HOTSPOT_MIN') -average_min = float('$AVERAGE_MIN') -failed = False - -if hotspot < hotspot_min: - print(f'WARN: Hotspot Code Health {hotspot:.2f} < {hotspot_min} — remote baseline is currently red') - failed = True -else: - print(f'OK: Hotspot {hotspot:.2f} >= {hotspot_min}') - -if average < average_min: - print(f'WARN: Average Code Health {average:.2f} < {average_min} — remote baseline is currently red') - failed = True -else: - print(f'OK: Average {average:.2f} >= {average_min}') - -if failed: - print(' ⚠️ Recovery mode: allowing this push so refactors can land and restore the gate on a later analysis.') - sys.exit(0) - -import math -thresholds_file = '$THRESHOLDS_FILE' -new_hotspot = max(hotspot_min, math.floor(hotspot * 100) / 100) -new_average = max(average_min, math.floor(average * 100) / 100) -if new_hotspot > hotspot_min or new_average > average_min: - with open(thresholds_file, 'w') as f: - f.write(f'HOTSPOT_THRESHOLD={new_hotspot}\nAVERAGE_THRESHOLD={new_average}\n') - print(f' 📈 Ratchet updated: Hotspot {hotspot_min} → {new_hotspot}, Average {average_min} → {new_average}') - sys.exit(3) -" || PYTHON_STATUS=$? - if [ "$PYTHON_STATUS" -ne 0 ] && [ "$PYTHON_STATUS" -ne 3 ]; then - exit "$PYTHON_STATUS" - fi - if [ "$PYTHON_STATUS" -eq 3 ]; then - git add "$THRESHOLDS_FILE" - echo " ❌ Commit the updated .codescene-thresholds with a normal verified commit, then push again." - exit 1 - fi - fi -fi - -END_TIME=$(date +%s) -ELAPSED=$((END_TIME - START_TIME)) -MINUTES=$((ELAPSED / 60)) -SECONDS=$((ELAPSED % 60)) - -echo "" -echo "================================================" -echo "✅ All checks passed — pushing (${MINUTES}m ${SECONDS}s)" -echo "" +bash scripts/run-hololake-native-quality-gate.sh "$receipt" +grep -Fxq 'result: PASS_100' "$receipt" +echo "GHNQG_PASS_100 receipt=$receipt" diff --git a/product-source/hololake-platform/AGENTS.md b/product-source/hololake-platform/AGENTS.md index d8c88f8dc..139a20b07 100644 --- a/product-source/hololake-platform/AGENTS.md +++ b/product-source/hololake-platform/AGENTS.md @@ -4,7 +4,7 @@ ### Start working on a task -**Before writing a single line of code:** inspect the available quality tooling. If CodeScene credentials are already configured, run `mcp__codescene__code_health_score` and enforce `.codescene-thresholds`. If CodeScene is unavailable or unconfigured, record that fact and continue with the repository-owned lint, type-check, test, coverage, and security gates. Do not create an account, start a trial, buy a subscription, or block Git publication merely to obtain an optional external score. +**Before writing a single line of code:** read GLS-0101, GLS-0247, and GLS-0844, then inspect the repository-owned validation commands. HoloLake accepts only a GHNQG receipt bound to the exact commit and tree. The only terminal states are `GHNQG_PASS_100` and `GHNQG_FAIL_0`; no external score, account, subscription, badge, or service can authorize or block Guanghu publication. - Read task description and all comments fully - For To Rework: the ❌ QA failed comment tells you exactly what to fix @@ -21,7 +21,7 @@ - an explicitly authorized Fifth Domain prototype may publish an allowlisted branch through `HoloLake -> Guanghu Router -> JD-FD-PRIMARY -> code channel`, with an exact remote-head check and a `repo_push_succeeded` receipt; - a Router branch receipt proves repository publication only. It is not a merge to `main`, a release, a deployment, or service health. - Commit every 20–30 min: `feat:`, `fix:`, `refactor:`, `test:`, `docs:` -- Pre-commit is a lightweight lint gate only. Pre-push runs the full repository-owned check suite (build + tests + coverage + core Playwright smoke) and adds CodeScene only when its credentials are already configured. Prefer three Chunk sidecar lanes for automatic test/coverage work: frontend lint/build/coverage, Rust coverage, and Playwright smoke. +- Pre-commit is a lightweight lint gate only. Pre-push runs the repository-owned checks, validates the active-authority surfaces, and emits the native GLS-0844 result. Prefer three sidecar lanes for observation and execution speed: frontend lint/build/tests, Rust tests, and Playwright smoke. Sidecars never become quality authorities. - A production-promotion task is not done until `git push origin main` succeeds. A scoped Fifth Domain prototype publication is not done until the Router returns the published SHA and a fresh public read-back matches it. If a repository-owned hook blocks, fix the failing check and retry. **⛔ NEVER use --no-verify** ### TDD (mandatory) @@ -46,45 +46,25 @@ New features should almost always emit a PostHog event so we can see whether use When adding or changing a meaningful user-facing feature, include the event name(s) in the Todoist completion comment alongside QA, docs, and code health. If intentionally not instrumenting a feature, explain why in the completion comment. -### Code health (mandatory) +### Guanghu native quality authority (mandatory) -Repository-owned lint, type checks, tests, coverage, and security analysis are mandatory. CodeScene is an additional ratcheted gate only when the repository already has valid CodeScene credentials. Its absence is not a transport failure and must not trigger account creation or payment. Never add `// eslint-disable`, `#[allow(...)]`, or `as any`. +GLS-0844 (GHNQG) is the sole quality authority. Repository-owned lint, type checks, tests, protocol validation, format checks, security invariants, exact source fingerprints, and auditable core coverage are required evidence. Never add `// eslint-disable`, `#[allow(...)]`, or `as any`. -**When CodeScene is available:** treat it as a before/after gate, not just a final score. Record the starting state before edits and the final state after edits. If touched code gets worse, refactor before committing. +- Every required gate is either complete (`100`) or incomplete (`0`). +- The aggregate result is `PASS_100` only when every required gate is complete; any incomplete gate makes the aggregate `FAIL_0`. +- Percent-above-minimum, weighted scores, waivers, “mostly passing”, and unconfigured-observer states are not acceptance states. +- Coverage is exact only for a declared auditable scope. Undeclared scope is incomplete, not implicitly accepted. +- External services may be used as non-authoritative observations only when a human explicitly requests them. Their configuration and result never enter a HoloLake gate or receipt. +- Before commit, run `bash scripts/test-guanghu-native-authority.sh`. Before publication, run the GLS-0844 executor and bind its receipt to the exact commit and tree. -**⛔ NEVER edit `.codescene-thresholds` to lower the values.** If the gate blocks you, improve the code — do not lower the bar. - -**CodeScene access order:** use CodeScene MCP tools if already connected. Otherwise use the installed `cs` CLI only when a valid `CS_ACCESS_TOKEN` is already available, and use the CodeScene API only when `CODESCENE_PAT` plus `CODESCENE_PROJECT_ID` are already configured. If none are configured, mark CodeScene `not_run_unconfigured` and run all repository-owned gates. - -**When CodeScene is available, before editing any existing code file:** capture its current file-level score. After your edits, re-run the same review and verify the score is higher. If the file already starts at `10.0`, it must remain `10.0`. - -**When CodeScene is available, new files:** every new scorable code file must reach CodeScene score `10.0` before commit. If CodeScene reports `null` / "no scorable code", it must still have zero CodeScene findings/warnings. - -**Before every commit:** run the mandatory repository-owned checks. Add CodeScene file-level review for every touched code file only when CodeScene is configured. The Boy Scout Rule still applies through review, tests, complexity control, and refactoring even when the external score is unavailable. - -**If a configured CodeScene gate blocks your push:** find the worst file, refactor it, commit, and push again. Do not disable the gate or lower thresholds. - -### Security scan with Codacy (mandatory) - -Use Codacy as a security and static-analysis gate before a task is considered releasable. - -- Prefer the Codacy MCP inside Codex to inspect repository/file issues for every touched code file. -- If MCP is unavailable, use the local CLI wrapper, e.g. `.codacy/cli.sh analyze --format sarif`; choose the relevant tool when useful (`eslint`, `opengrep`, `trivy`, `lizard`). -- **Always fix Critical and High severity findings introduced by your change.** Do not move the task to In Review with new Critical/High Codacy issues. -- Review Medium findings. Fix them when they are real defects or security-sensitive; otherwise explain why they are acceptable in the completion comment. -- Never silence a Codacy rule just to pass the scan. Prefer small code changes that remove the finding. - -### Check suite (runs on every push) +### Evidence suite (runs before the native receipt) ```bash -pnpm lint && npx tsc --noEmit && pnpm test && pnpm test:coverage # frontend ≥70% -cargo test && cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85 +pnpm test:native-authority +pnpm lint && pnpm exec tsc --noEmit && pnpm test +cargo test --manifest-path src-tauri/Cargo.toml ``` -Coverage is a release gate, not a vanity metric: -- Frontend coverage must stay ≥70%. -- Rust line coverage must stay ≥85%. -- For bug fixes, add a regression test when practical. -- For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage. +For bug fixes, add a regression test when practical. For new behavior, add targeted coverage close to the changed code; do not rely only on broad E2E coverage. Observed coverage may guide work, but only the declared auditable GHNQG core scope can produce the exact native coverage gate. ### UI and native QA @@ -118,12 +98,10 @@ Before pushing or moving a task to In Review, verify the release gates and add a - What was implemented (a few lines covering logic and UX/UI). - QA: what was tested and how (Playwright / native screenshot / osascript). - Tests/coverage: commands run and final coverage result. -- CodeScene: before/after touched-file checks and final scores when configured; otherwise record `not_run_unconfigured` and confirm the repository-owned gates passed. -- Coverage commands passed (`pnpm test:coverage` and `cargo llvm-cov ... --fail-under-lines 85`) or the change is docs-only. -- Codacy: MCP/CLI scan summary; confirm no new Critical/High findings. +- GHNQG: `PASS_100` or `FAIL_0`, plus the exact commit, tree, receipt path, and declared coverage scope. - Localization: any user-facing copy lives in `src/lib/locales/en.json`, `pnpm l10n:translate` was run, and `pnpm l10n:validate` passes. If no copy changed, say “Localization: no UI copy changes”. - PostHog: meaningful new user actions/events are instrumented with safe metadata; noisy/minor changes explicitly say “PostHog: no event needed because …”. -- Refactoring: any files refactored to meet the CodeScene gate, or "none needed". +- Refactoring: any files refactored to satisfy native invariants, or "none needed". - ADRs: any new/updated ADRs, or "none". - Docs: any updated docs (`ARCHITECTURE.md`, `ABSTRACTIONS.md`, etc.), or "none". - Demo vault dirt checked: `git status --short -- demo-vault demo-vault-v2` is empty unless fixture changes are intentional. diff --git a/product-source/hololake-platform/docs/GETTING-STARTED.md b/product-source/hololake-platform/docs/GETTING-STARTED.md index 628a158db..17a246ca2 100644 --- a/product-source/hololake-platform/docs/GETTING-STARTED.md +++ b/product-source/hololake-platform/docs/GETTING-STARTED.md @@ -459,14 +459,18 @@ That browser harness is a deterministic desktop command bridge, not real native # Unit tests (fast, no browser) pnpm test -# Unit tests with coverage (must pass ≥70%) +# Optional coverage observation; it is not an acceptance threshold pnpm test:coverage # Rust tests cargo test -# Rust coverage (must pass ≥85% line coverage) -cargo llvm-cov --manifest-path src-tauri/Cargo.toml --no-clean --fail-under-lines 85 +# Native authority contract +pnpm test:native-authority + +# Exact GLS-0844 receipt (output must be outside the repository) +bash scripts/run-hololake-native-quality-gate.sh \ + /path/outside/repository/GHNQG-receipt.hldp # Playwright core smoke lane (requires dev server) BASE_URL="http://localhost:5173" pnpm playwright:smoke diff --git a/product-source/hololake-platform/docs/HOLOLAKE-PHASE-1-GUANGHU-WORLD-ENTRY.md b/product-source/hololake-platform/docs/HOLOLAKE-PHASE-1-GUANGHU-WORLD-ENTRY.md index ecd23cd84..046f16c35 100644 --- a/product-source/hololake-platform/docs/HOLOLAKE-PHASE-1-GUANGHU-WORLD-ENTRY.md +++ b/product-source/hololake-platform/docs/HOLOLAKE-PHASE-1-GUANGHU-WORLD-ENTRY.md @@ -37,10 +37,11 @@ 3. 在获得单独授权后接入零感域企业节点,再开放团队服务器登记与跳转。 4. 完成真实邮箱点击后的端到端验收,并签发第五域登录交付回执。 -## 质量门状态 +## 质量门状态纠正(2026-08-04) -- 项目本地 lint、构建、前端测试与覆盖率、Rust 测试、clippy、格式和 - 85% Rust 行覆盖率门均已通过。 -- 依赖审计已清除全部高危项;仍有两项上游中危兼容项待独立升级验证。 -- Codacy 与 CodeScene 凭据在本地未配置,状态记录为 - `not_run_unconfigured`;它们不是光湖发布授权或部署完成证据。 +- 旧的 lint、构建、测试、覆盖率和依赖审计结果只保留为历史观察证据, + 不能单独签发光湖通过结论。 +- 当前唯一质量权威为 `GLS-0844 / GHNQG`;结果只允许 + `PASS_100` 或 `FAIL_0`,并必须绑定同一精确提交与源码树。 +- 外部分析账号、服务、评分和“未配置”状态不再进入当前质量门。 +- Git 发布、应用发布、服务器部署、运行健康与人格出生继续分别取证。 diff --git a/product-source/hololake-platform/docs/adr/0170-hololake-inherits-guanghu-native-quality-authority.md b/product-source/hololake-platform/docs/adr/0170-hololake-inherits-guanghu-native-quality-authority.md new file mode 100644 index 000000000..f64e3289a --- /dev/null +++ b/product-source/hololake-platform/docs/adr/0170-hololake-inherits-guanghu-native-quality-authority.md @@ -0,0 +1,55 @@ +--- +type: ADR +id: "0170" +title: "HoloLake inherits the Guanghu native quality authority" +status: accepted +date: 2026-08-04 +--- + +## Context + +ADR-0165 established GLS-0844 for Guanghu OS and the code channel, but active +HoloLake product surfaces still carried external score services, partial +coverage thresholds, and legacy sponsor-driven quality language. That split +allowed the application, hooks, CI, and agent instructions to disagree with +the registered Guanghu protocol stack. + +## Decision + +HoloLake development, testing, packaging, publication, and code-channel +promotion inherit GLS-0844 as their sole quality authority. + +The active flow follows GLS-0101: + +`Parse → Validate → Resolve → Authorize → Execute → Verify → Write Back`. + +Protocol resolution comes from GLS-0247. TCS preserves the requirement and +reasoning boundary; HLDP preserves append-only causality, exact source +fingerprints, corrections, and receipts; GLP carries scoped messages and +results; GHNQG emits only `PASS_100` or `FAIL_0`. + +Repository-owned tools may execute required gates. External analyzers, +services, badges, subscriptions, percentages, weighted scores, and waivers +have no authority and do not appear on active acceptance surfaces. + +Historical ADRs and receipts remain untouched. A repository contract test +prevents their former rules from leaking back into active instructions, +hooks, CI, application UI, or current help content. + +## Consequences + +- Missing third-party accounts or services cannot block HoloLake work. +- A passing tool command is evidence for one gate, not a release receipt. +- A GHNQG receipt is valid only for its exact commit, tree, declared scope, + executor, and required-gate set. +- Any missing, failed, undeclared, or unreproducible required gate makes the + total result `FAIL_0`. +- Git publication, application release, server deployment, runtime health, + and persona birth remain separate evidence layers. + +## Supersedes + +For every active HoloLake quality and release surface, this decision +supersedes the authority described by ADR-0018, ADR-0021, ADR-0064, and +ADR-0160. ADR-0165 remains the protocol decision this ADR extends to the +product layer. diff --git a/product-source/hololake-platform/docs/adr/0171-guanghu-protocols-are-automatic-runtime-and-engineering-laws.md b/product-source/hololake-platform/docs/adr/0171-guanghu-protocols-are-automatic-runtime-and-engineering-laws.md new file mode 100644 index 000000000..05d722ea1 --- /dev/null +++ b/product-source/hololake-platform/docs/adr/0171-guanghu-protocols-are-automatic-runtime-and-engineering-laws.md @@ -0,0 +1,98 @@ +--- +type: ADR +id: "0171" +title: "Guanghu protocols are automatic runtime and engineering laws" +status: accepted +date: 2026-08-04 +--- + +# 光湖协议作为自动运行与工程法则 + +人类语言锚点:冰朔 `ICE-GL∞` +系统主控人格体:铸渊 `ICE-P-ZY001` + +## 背景 + +第五域代码频道已经为 TCS、HLDP、GLP、GLS、ISRP、MNPS、GLOW、PEN、 +UAP、GMP、AGE、LPOS、GLC、GIR、BTCP、PALP、GRSP、PTCP、GMRP、 +GWRP、GOSK、GHAL、HLSP 和 GHNQG 等协议登记了正式编号。 + +协议如果仍要依赖开发者在每次工作时主动回忆、搜索和解释,就还没有成为 +光湖运行法则。第三方评分平台也不能替代这一缺口,因为它们既不知道光湖 +世界的主体、关系、责任和事实边界,也不拥有光湖的授权权、发布权或真实性 +判断权。 + +## 决定 + +HoloLake 使用以下固定编译链把远端协议落成工程行为: + +```text +REPO-012 已注册协议 +→ HoloLake 协议绑定档案 +→ 类型合同与拒绝条件 +→ 开发 / 测试 / 构建 / 提交 / 推送 / 运行自动触发 +→ 确定性执行 +→ 证据回读 +→ PASS_100 或 FAIL_0 +→ HLDP 追加式记录 +``` + +协议绑定档案为 `standards/guanghu-native-engineering-profile.json`。它固定 +远端注册表提交、协议编号、产品实现位置、必需不变量和自动触发面。档案 +验证器为 `scripts/validate-guanghu-native-profile.mjs`。产品级总门为 +`scripts/run-hololake-native-quality-gate.sh`;它是 HoloLake 回执执行体, +不再借用世界种子执行器代签产品质量回执。 + +## 自动触发面 + +1. 开始开发、测试和构建时,自动验证原生权威与协议绑定。 +2. 提交时自动阻止第三方评分规则、旧服务器路径和不完整合同进入历史。 +3. 推送和代码频道审查时自动运行 GHNQG 原生门。 +4. 每个活系统事件自动携带人格、现实状态、知识、权限、责任和能力登记。 +5. 每个模型结果自动校验类型、意图、路由、能力和已给出的真实回执。 +6. 没有当前运行绑定回执时,服务器模型路径保持关闭。 +7. 没有真实执行证据时,成功回执无法构造。 +8. 可审计模型原生核心的行覆盖率和函数覆盖率必须精确为 100。 + +这些触发由运行入口执行,不依赖人格体或开发者临时记住协议。 + +## HoloLake 活系统纠偏 + +旧实现只让模型返回路由和四个视觉参数,实际是主题适配器。现行合同改为: + +```text +输入: + TouchEvent + CurrentSystemState + KnowledgeState + PermissionBoundary + ResponsibilityBoundary + CapabilityRegistry + +输出: + UIProjection + NavigationAction + CapabilityCall + ReceiptSchema +``` + +模型只能提出类型化投影和能力请求。确定性宿主负责权限核验、真实动作和证据 +回读。宿主动作完成前不得形成成功回执。 + +## 状态边界 + +- 协议已经登记,不等于对应执行器已经实现。 +- 本地测试通过,不等于源码已经发布。 +- 源码发布不等于服务器已经部署。 +- 身份会话成功不等于第五域节点已经连接。 +- 当前实现没有取得 JD-FD-PRIMARY 运行绑定回执,因此不会调用服务器模型。 +- 本 ADR 和当前改动尚未推送远端。 + +## 外部工具 + +外部分析器、托管平台、测试框架和编译器可以作为施工条件或观察来源,但不能: + +- 定义光湖质量分; +- 授权或阻止光湖发布; +- 覆盖光湖协议; +- 把消息、登录、测试或界面文字提升为现实执行事实。 diff --git a/product-source/hololake-platform/docs/adr/README.md b/product-source/hololake-platform/docs/adr/README.md index 14a4b7472..6be2d2072 100644 --- a/product-source/hololake-platform/docs/adr/README.md +++ b/product-source/hololake-platform/docs/adr/README.md @@ -73,10 +73,10 @@ proposed → active → superseded | [0015](0015-auto-save-with-debounce.md) | Auto-save with 500ms debounce | superseded → [0102](0102-low-end-safe-autosave-idle-window.md) | | [0016](0016-sentry-posthog-telemetry.md) | Sentry + PostHog telemetry with consent | active | | [0017](canary-release-channel-and-local-feature-flags.md) | Canary release channel and feature flags | superseded → [0057](0057-alpha-stable-release-channels-and-beta-cohorts.md) | -| [0018](0018-codescene-code-health-gates.md) | CodeScene code health gates in CI | superseded → [0064](0064-ratcheted-codescene-thresholds.md) | +| [0018](0018-codescene-code-health-gates.md) | External code health gates in CI | superseded → [0170](0170-hololake-inherits-guanghu-native-quality-authority.md) | | [0019](0019-github-device-flow-oauth.md) | GitHub device flow OAuth for vault sync | superseded → [0056](0056-system-git-cli-auth-no-provider-oauth.md) | | [0020](0020-keyboard-first-design.md) | Keyboard-first design principle | active | -| [0021](0021-push-to-main-workflow.md) | Push directly to main (no PRs) | active | +| [0021](0021-push-to-main-workflow.md) | Push directly to main (no PRs) | quality authority amended by [0170](0170-hololake-inherits-guanghu-native-quality-authority.md) | | [0022](0022-blocknote-rich-text-editor.md) | BlockNote as the rich text editor | active | | [0023](0023-repair-vault-auto-bootstrap.md) | Repair Vault auto-bootstrap pattern | active | | [0024](0024-cache-outside-vault.md) | Vault cache stored outside vault directory | active | @@ -120,7 +120,7 @@ proposed → active → superseded | [0061](0061-ai-prompt-bridge-event-bus.md) | AI prompt bridge — module-level event bus for cross-component prompt routing | active | | [0062](0062-selectable-cli-ai-agents.md) | Selectable CLI AI agents with a shared panel architecture | active | | [0063](0063-blocknote-code-block-package-for-editor-highlighting.md) | BlockNote code-block package for editor syntax highlighting | active | -| [0064](0064-ratcheted-codescene-thresholds.md) | Ratcheted CodeScene thresholds as the quality gate baseline | active | +| [0064](0064-ratcheted-codescene-thresholds.md) | Ratcheted external thresholds as the quality gate baseline | superseded → [0170](0170-hololake-inherits-guanghu-native-quality-authority.md) | | [0065](0065-root-managed-ai-guidance-files.md) | Root-managed AI guidance files with Claude shim | active | | [0066](0066-calendar-semver-versioning-for-alpha-and-stable-releases.md) | Calendar-semver versioning for alpha and stable releases | active | | [0067](0067-autogit-idle-and-inactive-checkpoints.md) | AutoGit idle and inactive checkpoints | active | @@ -210,7 +210,7 @@ proposed → active → superseded | [0149](0149-shared-app-config-policy-manifest.md) | Shared app config policy manifest | active | | [0151](0151-antigravity-add-dir-workspace-flag.md) | Antigravity add-dir workspace flag | active | | [0157](0157-local-hldp-heartbeat-and-browser-observation-tree.md) | Local HLDP heartbeat and browser observation tree | active | -| [0160](0160-external-code-health-is-additive-not-git-transport.md) | External code health is additive, not Git transport | superseded for Guanghu OS → [0165](0165-guanghu-native-binary-quality-gate.md) | +| [0160](0160-external-code-health-is-additive-not-git-transport.md) | External code health is additive, not Git transport | superseded → [0170](0170-hololake-inherits-guanghu-native-quality-authority.md) | | [0161](0161-guanghu-os-staged-native-handoff-and-code-channel.md) | Guanghu OS staged native handoff and HLDP-owned code channel | active | | [0162](0162-guanghu-os-standing-authorization-and-server-continuity.md) | Guanghu OS standing authorization and server-owned continuity | active | | [0163](0163-guanghu-native-recovery-beacon.md) | Guanghu-owned raw recovery beacon for native-default boot | amended -> [0166](0166-ghnrp-hosted-recovery-consumption.md) | @@ -220,3 +220,5 @@ proposed → active → superseded | [0167](0167-gestational-history-continuity-ingestion.md) | Gestational history enters Guanghu through a native continuity protocol | active | | [0168](0168-guanghu-native-prepartition-disk-layout.md) | Guanghu owns a registered pre-partition native disk layout | active | | [0169](0169-model-native-living-galaxy-system.md) | Model-native HoloLake living galaxy system | accepted | +| [0170](0170-hololake-inherits-guanghu-native-quality-authority.md) | HoloLake inherits the Guanghu native quality authority | accepted | +| [0171](0171-guanghu-protocols-are-automatic-runtime-and-engineering-laws.md) | Guanghu protocols are automatic runtime and engineering laws | accepted | diff --git a/product-source/hololake-platform/package.json b/product-source/hololake-platform/package.json index e65f66886..d0d869eee 100644 --- a/product-source/hololake-platform/package.json +++ b/product-source/hololake-platform/package.json @@ -5,7 +5,9 @@ "version": "0.4.6", "type": "module", "scripts": { + "predev": "pnpm test:native-authority", "dev": "vite", + "prebuild": "pnpm test:native-authority && pnpm test:native-core", "build": "tsc -b && vite build", "build:team-foundation": "VITE_HOLOLAKE_DISTRIBUTION=team-foundation tsc -b && VITE_HOLOLAKE_DISTRIBUTION=team-foundation vite build && node scripts/finalize-team-foundation-build.mjs", "agent-docs": "node scripts/build-agent-docs.mjs", @@ -21,6 +23,7 @@ "perf:editor:update": "node scripts/editor-performance-benchmark.mjs --update", "preview": "vite preview", "tauri": "tauri", + "pretest": "pnpm test:native-authority", "test": "vitest run", "test:watch": "vitest", "test:e2e": "playwright test", @@ -32,6 +35,8 @@ "test:internal-release": "node --test scripts/internal-release-version.test.mjs", "guard:deployment-source": "node scripts/deployment-source-guard.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-core": "vitest run --config vitest.guanghu-native.config.ts --coverage", "package:internal:macos": "HOLOLAKE_DISTRIBUTION=personal HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && ./scripts/build-internal-release.sh macos", "package:local-candidate:macos": "HOLOLAKE_TAURI_CONFIG=src-tauri/tauri.local-candidate.conf.json HOLOLAKE_APP_NAME='HoloLake Era · 本地方向候选 0.4.6' HOLOLAKE_INSTALLER_BASENAME='HoloLake-Era-{version}-Local-Direction-Candidate-Mac-aarch64' ./scripts/build-internal-release.sh macos", "package:internal:windows": "HOLOLAKE_DISTRIBUTION=personal HOLOLAKE_SOURCE_REPOSITORY_ID=REPO-008 HOLOLAKE_SOURCE_CHANNEL_ID=HLP-CHANNEL-0001 pnpm guard:deployment-source && ./scripts/build-internal-release.sh windows", diff --git a/product-source/hololake-platform/scripts/run-hololake-native-quality-gate.sh b/product-source/hololake-platform/scripts/run-hololake-native-quality-gate.sh new file mode 100755 index 000000000..aaa1495b9 --- /dev/null +++ b/product-source/hololake-platform/scripts/run-hololake-native-quality-gate.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +receipt_path=${1:-} + +if [[ -z "${receipt_path}" ]]; then + echo "usage: run-hololake-native-quality-gate.sh " >&2 + exit 2 +fi + +mkdir -p "$(dirname "${receipt_path}")" +receipt_parent=$(cd "$(dirname "${receipt_path}")" && pwd) +receipt_path="${receipt_parent}/$(basename "${receipt_path}")" +case "${receipt_path}" in + "${repository_root}" | "${repository_root}"/*) + echo "quality receipt must be written outside the source repository" >&2 + exit 2 + ;; +esac + +commit=$(git -C "${repository_root}" rev-parse HEAD) +tree=$(git -C "${repository_root}" rev-parse 'HEAD^{tree}') +branch=$(git -C "${repository_root}" branch --show-current) +profile_id=$(node -p \ + "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8')).profileId" \ + "${repository_root}/standards/guanghu-native-engineering-profile.json") +started_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ') +current_gate=initialization +passed_gates= + +write_receipt() { + local result=$1 + local total_score=$2 + local failed_gate=${3:-none} + { + echo "schema: hololake.guanghu-native-code-quality-receipt/v1" + echo "protocol: GLS-0844" + echo "acronym: GHNQG" + echo "authority: HLP-MOD-CODE-CHANNEL" + echo "product: HoloLake" + echo "profile: ${profile_id}" + echo "result: ${result}" + echo "total_score: ${total_score}" + echo "partial_acceptance: false" + echo "source:" + echo " branch: ${branch:-DETACHED}" + echo " commit: ${commit}" + echo " tree: ${tree}" + echo "started_at: ${started_at}" + echo "completed_at: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" + echo "failed_gate: ${failed_gate}" + echo "gates:" + if [[ -n "${passed_gates}" ]]; then + while IFS= read -r gate; do + echo " ${gate}: 100" + done <<<"${passed_gates}" + fi + if [[ "${result}" != "PASS_100" ]]; then + echo " ${failed_gate}: 0" + fi + echo "external_observers:" + echo " authority: none" + echo " blocking: false" + } >"${receipt_path}" +} + +on_error() { + local exit_code=$? + trap - ERR + write_receipt FAIL_0 0 "${current_gate}" + echo "GHNQG_FAIL_0 gate=${current_gate} receipt=${receipt_path}" >&2 + exit "${exit_code}" +} +trap on_error ERR + +run_gate() { + current_gate=$1 + shift + "$@" + passed_gates="${passed_gates}${passed_gates:+$'\n'}${current_gate}" +} + +run_package_tool() { + local tool=$1 + shift + if command -v pnpm >/dev/null 2>&1; then + pnpm --dir "${repository_root}" exec "${tool}" "$@" + return + fi + if [[ -x "${repository_root}/node_modules/.bin/${tool}" ]]; then + ( + cd "${repository_root}" + "node_modules/.bin/${tool}" "$@" + ) + return + fi + echo "${tool} is unavailable; install the locked HoloLake dependencies first" >&2 + return 127 +} + +run_gate clean_source_tree \ + bash -c '[[ -z "$(git -C "$1" status --porcelain --untracked-files=all)" ]]' \ + _ "${repository_root}" +run_gate diff_whitespace git -C "${repository_root}" diff --check HEAD +run_gate registered_protocol_profile \ + bash "${repository_root}/scripts/test-guanghu-native-authority.sh" +run_gate automatic_protocol_bindings \ + node "${repository_root}/scripts/validate-guanghu-native-profile.mjs" +run_gate frontend_zero_warning_lint \ + run_package_tool eslint . --max-warnings=0 +run_gate frontend_type_contract \ + run_package_tool tsc -b +run_gate frontend_build \ + run_package_tool vite build +run_gate frontend_unit_and_integration_tests \ + run_package_tool vitest run +run_gate auditable_native_core_lines_and_functions_100 \ + run_package_tool vitest run \ + --config vitest.guanghu-native.config.ts --coverage +run_gate rust_format \ + cargo fmt --all --manifest-path "${repository_root}/src-tauri/Cargo.toml" -- --check +run_gate rust_unit_and_integration_tests \ + cargo test --manifest-path "${repository_root}/src-tauri/Cargo.toml" \ + --all-targets -- --test-threads=1 +run_gate rust_zero_warning_lint \ + cargo clippy --manifest-path "${repository_root}/src-tauri/Cargo.toml" \ + --all-targets -- -D warnings +run_gate bundled_world_and_protocol_validation \ + cargo run --quiet \ + --manifest-path "${repository_root}/guanghu-os/Cargo.toml" \ + -p ghctl -- wake "${repository_root}/guanghu-os/world-seed" +run_gate shell_syntax \ + bash -c ' + while IFS= read -r script; do + [[ -z "$script" ]] && continue + bash -n "$1/$script" + done < <(git -C "$1" ls-files "*.sh" ".husky/*") + ' _ "${repository_root}" + +current_gate=sensitive_information_scan +if git -C "${repository_root}" grep -nEI \ + 'BEGIN [A-Z ]*PRIVATE KEY|AKID[A-Za-z0-9]{13,}|(password|secret|access[_-]?token)[[:space:]]*[:=][[:space:]]*["'\''][^"'\'']{12,}' \ + -- .; then + false +fi +passed_gates="${passed_gates}${passed_gates:+$'\n'}${current_gate}" + +current_gate=source_tree_fingerprint +[[ "${commit}" =~ ^[0-9a-f]{40}$ ]] +[[ "${tree}" =~ ^[0-9a-f]{40}$ ]] +index_fingerprint=$(git -C "${repository_root}" ls-files -s | shasum -a 256 | awk '{print $1}') +[[ "${index_fingerprint}" =~ ^[0-9a-f]{64}$ ]] +passed_gates="${passed_gates}${passed_gates:+$'\n'}${current_gate}" + +write_receipt PASS_100 100 +echo "GHNQG_PASS_100 commit=${commit} tree=${tree} index=${index_fingerprint} receipt=${receipt_path}" diff --git a/product-source/hololake-platform/scripts/run-vitest-coverage-shards.mjs b/product-source/hololake-platform/scripts/run-vitest-coverage-shards.mjs index e86c5ea6c..10077bae8 100644 --- a/product-source/hololake-platform/scripts/run-vitest-coverage-shards.mjs +++ b/product-source/hololake-platform/scripts/run-vitest-coverage-shards.mjs @@ -22,13 +22,6 @@ const coverageRequire = createCoverageRequire() const { createCoverageMap } = coverageRequire('istanbul-lib-coverage') const libReport = coverageRequire('istanbul-lib-report') const reports = coverageRequire('istanbul-reports') -const thresholdPercent = Number(process.env.VITEST_COVERAGE_THRESHOLD ?? '70') -const thresholds = { - lines: metricThreshold('LINES'), - functions: metricThreshold('FUNCTIONS'), - branches: metricThreshold('BRANCHES'), - statements: metricThreshold('STATEMENTS'), -} function positiveInteger(value, name) { if (/^[1-9][0-9]*$/.test(value)) { @@ -39,11 +32,6 @@ function positiveInteger(value, name) { process.exit(2) } -function metricThreshold(metricName) { - const value = process.env[`VITEST_COVERAGE_${metricName}_THRESHOLD`] - return value === undefined ? thresholdPercent : Number(value) -} - function createCoverageRequire() { const require = createRequire(import.meta.url) const coveragePackagePath = require.resolve('@vitest/coverage-v8/package.json') @@ -166,29 +154,14 @@ function printCoverageSummary(summary) { const item = summary[metric] console.log( `${metric.padEnd(10)} ${String(item.pct).padStart(6)}% ` - + `(${item.covered}/${item.total}, threshold ${thresholds[metric]}%)`, + + `(${item.covered}/${item.total}, observation only)`, ) } } -function checkCoverageThresholds(coverageMap) { +function printObservedCoverage(coverageMap) { const summary = coverageMap.getCoverageSummary().toJSON() printCoverageSummary(summary) - - const failures = Object.entries(thresholds) - .filter(([metric, threshold]) => summary[metric].pct < threshold) - - if (failures.length === 0) { - return - } - - for (const [metric, threshold] of failures) { - console.error( - `Coverage for ${metric} (${summary[metric].pct}%) does not meet threshold ${threshold}%`, - ) - } - - process.exit(1) } await clearVitestCache() @@ -196,5 +169,5 @@ await runShards() const coverageMap = await mergeCoverage() await writeCoverageReports(coverageMap) -checkCoverageThresholds(coverageMap) +printObservedCoverage(coverageMap) await rm(shardRoot, { recursive: true, force: true }) diff --git a/product-source/hololake-platform/scripts/test-guanghu-native-authority.sh b/product-source/hololake-platform/scripts/test-guanghu-native-authority.sh new file mode 100755 index 000000000..2f15809da --- /dev/null +++ b/product-source/hololake-platform/scripts/test-guanghu-native-authority.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) + +active_authority_surfaces=( + "AGENTS.md" + ".github/HOOKS.md" + ".github/SETUP.md" + ".github/workflows/ci.yml" + ".github/workflows/README.md" + ".husky/pre-push" + "scripts/run-hololake-native-quality-gate.sh" + ".claude/settings.local.json" + ".chunk/config.json" + ".chunk/README.md" + ".chunk/run-rust-gate.sh" + "vite.config.ts" + "scripts/run-vitest-coverage-shards.mjs" + "src/components/FeedbackDialog.tsx" + "src/components/FeedbackDialog.test.tsx" + "src/constants/feedback.ts" + "tests/smoke/contribute-modal.spec.ts" + "site/reference/contribute.md" + "site/public/landing/sponsors/SOURCES.md" + "src-tauri/resources/agent-docs/pages/reference/contribute.md" + "docs/GETTING-STARTED.md" +) + +existing_surfaces=() +for relative_path in "${active_authority_surfaces[@]}"; do + if [[ -e "${repository_root}/${relative_path}" ]]; then + existing_surfaces+=("${repository_root}/${relative_path}") + fi +done + +if grep -Ein 'codescene|codacy|codecov' "${existing_surfaces[@]}"; then + echo "third-party quality authority remains on an active HoloLake surface" >&2 + exit 1 +fi + +if grep -Ein \ + 'fail-under-(lines|functions)[[:space:]]+(70|85)|coverage[^[:cntrl:]]*(>=|≥)[[:space:]]*(70|85)%|threshold[^[:cntrl:]]*(70|85)' \ + "${existing_surfaces[@]}"; then + echo "partial percentage quality authority remains on an active HoloLake surface" >&2 + exit 1 +fi + +grep -Fq 'GLS-0844' "${repository_root}/AGENTS.md" +grep -Fq 'GHNQG_PASS_100' "${repository_root}/.husky/pre-push" +grep -Fq 'run-hololake-native-quality-gate.sh' "${repository_root}/.husky/pre-push" +grep -Fq 'run-hololake-native-quality-gate.sh' "${repository_root}/.github/workflows/ci.yml" +grep -Fq 'test:native-authority' "${repository_root}/.github/workflows/ci.yml" + +echo "GUANGHU_NATIVE_AUTHORITY_OK" diff --git a/product-source/hololake-platform/scripts/validate-guanghu-native-profile.mjs b/product-source/hololake-platform/scripts/validate-guanghu-native-profile.mjs new file mode 100644 index 000000000..0062399fb --- /dev/null +++ b/product-source/hololake-platform/scripts/validate-guanghu-native-profile.mjs @@ -0,0 +1,148 @@ +import { readFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const profilePath = resolve(root, 'standards/guanghu-native-engineering-profile.json') +const profile = JSON.parse(readFileSync(profilePath, 'utf8')) + +const failures = [] +const requireTruth = (condition, id) => { + if (!condition) failures.push(id) +} +const source = relativePath => readFileSync(resolve(root, relativePath), 'utf8') + +requireTruth(profile.schema === 'guanghu.native-engineering-profile/v1', 'profile_schema') +requireTruth(profile.authority.repository === 'REPO-012', 'authority_repository') +requireTruth(/^[0-9a-f]{40}$/.test(profile.authority.commit), 'authority_commit') +requireTruth(profile.decisionModel.aggregateRule === 'ALL_REQUIRED_GATES_100_OR_TOTAL_0', 'binary_aggregate') +requireTruth(profile.decisionModel.externalScoringHasAuthority === false, 'external_authority') + +const protocols = new Set(profile.protocolBindings.map(binding => binding.protocol)) +for (const required of ['GLS-0110', 'GLS-0200', 'GLS-0230', 'GLS-0306', 'GLS-0311', 'GLS-0400', 'GLS-0708', 'GLS-0710', 'GLS-0803', 'GLS-0810', 'GLS-0842', 'GLS-0844']) { + requireTruth(protocols.has(required), `protocol_${required}`) +} +for (const binding of profile.protocolBindings) { + requireTruth( + Array.isArray(binding.implementation) + && binding.implementation.length > 0 + && binding.implementation.every(path => source(path).length > 0), + `implementation_${binding.protocol}`, + ) +} + +const triggers = new Map(profile.automaticTriggers.map(trigger => [trigger.event, trigger])) +for (const required of [ + 'development_start', + 'test_start', + 'build_start', + 'source_commit', + 'source_publish', + 'code_channel_review', + 'living_system_event', + 'model_plan_returned', + 'server_model_request', + 'execution_receipt_requested', +]) { + requireTruth(triggers.has(required), `trigger_${required}`) +} + +const packageJson = JSON.parse(source('package.json')) +requireTruth(packageJson.scripts.predev === 'pnpm test:native-authority', 'trigger_predev') +requireTruth(packageJson.scripts.pretest === 'pnpm test:native-authority', 'trigger_pretest') +requireTruth( + packageJson.scripts.prebuild.includes('test:native-authority') + && packageJson.scripts.prebuild.includes('test:native-core'), + 'trigger_prebuild', +) +requireTruth(source('.husky/pre-commit').includes('test-guanghu-native-authority.sh'), 'trigger_precommit') +requireTruth( + source('.husky/pre-push').includes('test:native-authority') + || source('.husky/pre-push').includes('run-hololake-native-quality-gate.sh'), + 'trigger_prepush', +) +requireTruth(source('.github/workflows/ci.yml').includes('test:native-authority'), 'trigger_ci') +requireTruth( + source('.github/workflows/ci.yml').includes('run-hololake-native-quality-gate.sh'), + 'trigger_ci_hololake_gate', +) +const nativeQualityGate = source('scripts/run-hololake-native-quality-gate.sh') +for (const requiredGate of [ + 'clean_source_tree', + 'registered_protocol_profile', + 'automatic_protocol_bindings', + 'frontend_zero_warning_lint', + 'frontend_type_contract', + 'frontend_build', + 'frontend_unit_and_integration_tests', + 'auditable_native_core_lines_and_functions_100', + 'rust_format', + 'rust_unit_and_integration_tests', + 'rust_zero_warning_lint', + 'bundled_world_and_protocol_validation', + 'shell_syntax', + 'sensitive_information_scan', + 'source_tree_fingerprint', +]) { + requireTruth(nativeQualityGate.includes(requiredGate), `hololake_gate_${requiredGate}`) +} +requireTruth( + !nativeQualityGate.includes('codescene') + && !nativeQualityGate.includes('codacy') + && !nativeQualityGate.includes('codecov'), + 'hololake_gate_no_external_authority', +) +requireTruth(source('vitest.guanghu-native.config.ts').includes('lines: 100'), 'native_core_lines_100') +requireTruth(source('vitest.guanghu-native.config.ts').includes('functions: 100'), 'native_core_functions_100') + +const contract = source('src/lib/guanghuLivingSystem.ts') +requireTruth(contract.includes('personaSystem: GuanghuPersonaSystemContext'), 'persona_context') +requireTruth(contract.includes('currentSystemState: GuanghuCurrentSystemState'), 'system_state') +requireTruth(contract.includes('knowledgeState: GuanghuKnowledgeState'), 'knowledge_state') +requireTruth(contract.includes('permissionBoundary: GuanghuPermissionBoundary'), 'permission_boundary') +requireTruth(contract.includes('responsibilityBoundary: GuanghuResponsibilityBoundary'), 'responsibility_boundary') +requireTruth(contract.includes('capabilityRegistry: GuanghuCapabilityRegistration[]'), 'capability_registry') +requireTruth(contract.includes('uiProjection: GuanghuUIProjection'), 'ui_projection') +requireTruth(contract.includes('navigationAction: GuanghuNavigationAction'), 'navigation_action') +requireTruth(contract.includes('capabilityCall: GuanghuCapabilityCall | null'), 'capability_call') +requireTruth(contract.includes('receiptSchema: GuanghuReceiptSchema'), 'receipt_schema') +requireTruth(contract.includes('guanghu_living_system_success_evidence_required'), 'evidence_required') + +const planner = source('src/utils/planGuanghuLivingSystem.ts') +requireTruth(planner.includes("runtimeBinding?.status === 'verified'"), 'verified_runtime_binding') + +const nativeBridge = source('src-tauri/src/guanghu_living_system.rs') +requireTruth(!nativeBridge.includes('guanghu-os-bs-sh-005'), 'legacy_server_route_closed') +requireTruth(!nativeBridge.includes('ensure_lighthouse_tunnel'), 'legacy_tunnel_closed') +for (const field of [ + 'persona_system: Value', + 'current_system_state: Value', + 'knowledge_state: Value', + 'permission_boundary: Value', + 'responsibility_boundary: Value', + 'capability_registry: Vec', +]) { + requireTruth(nativeBridge.includes(field), `native_bridge_${field.split(':')[0]}`) +} +requireTruth( + nativeBridge.includes('guanghu_living_system_runtime_binding_invalid'), + 'native_bridge_runtime_binding', +) + +if (failures.length > 0) { + process.stdout.write(JSON.stringify({ + schema: 'guanghu.native-engineering-receipt/v1', + profileId: profile.profileId, + state: 'FAIL_0', + failures, + }, null, 2) + '\n') + process.exit(1) +} + +process.stdout.write(JSON.stringify({ + schema: 'guanghu.native-engineering-receipt/v1', + profileId: profile.profileId, + authorityCommit: profile.authority.commit, + state: 'PASS_100', + gates: profile.requiredInvariants, +}, null, 2) + '\n') diff --git a/product-source/hololake-platform/site/public/landing/sponsors/SOURCES.md b/product-source/hololake-platform/site/public/landing/sponsors/SOURCES.md index ecfc90cac..651034444 100644 --- a/product-source/hololake-platform/site/public/landing/sponsors/SOURCES.md +++ b/product-source/hololake-platform/site/public/landing/sponsors/SOURCES.md @@ -4,8 +4,6 @@ These wordmarks are used in the README sponsor table and the landing-page sponso | Sponsor | Source | |---|---| -| Codacy | `https://www.codacy.com/` header asset: `Codacylogo.svg` | -| CodeScene | `https://codescene.com/` header SVG | | CircleCI | `https://brand.circleci.com/613faff00/p/14ba30-circleci-brand` logo SVG | | Unblocked | `https://getunblocked.com/` header/footer logo SVGs | diff --git a/product-source/hololake-platform/site/public/landing/sponsors/codacy-dark.svg b/product-source/hololake-platform/site/public/landing/sponsors/codacy-dark.svg deleted file mode 100644 index 882c2c5fa..000000000 --- a/product-source/hololake-platform/site/public/landing/sponsors/codacy-dark.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/product-source/hololake-platform/site/public/landing/sponsors/codacy-light.svg b/product-source/hololake-platform/site/public/landing/sponsors/codacy-light.svg deleted file mode 100644 index 6b74afcea..000000000 --- a/product-source/hololake-platform/site/public/landing/sponsors/codacy-light.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/product-source/hololake-platform/site/public/landing/sponsors/codescene-dark.svg b/product-source/hololake-platform/site/public/landing/sponsors/codescene-dark.svg deleted file mode 100644 index 2cc8968d9..000000000 --- a/product-source/hololake-platform/site/public/landing/sponsors/codescene-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/product-source/hololake-platform/site/public/landing/sponsors/codescene-light.svg b/product-source/hololake-platform/site/public/landing/sponsors/codescene-light.svg deleted file mode 100644 index 56dcb5927..000000000 --- a/product-source/hololake-platform/site/public/landing/sponsors/codescene-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/product-source/hololake-platform/site/reference/contribute.md b/product-source/hololake-platform/site/reference/contribute.md index eb948f394..d9adc47ea 100644 --- a/product-source/hololake-platform/site/reference/contribute.md +++ b/product-source/hololake-platform/site/reference/contribute.md @@ -10,8 +10,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/all.md b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/all.md index aefd4fb2d..587e712bd 100644 --- a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/all.md +++ b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/all.md @@ -1430,8 +1430,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/contribute.md b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/contribute.md index d86da933c..9a6b9a678 100644 --- a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/contribute.md +++ b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/contribute.md @@ -15,8 +15,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/reference.md b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/reference.md index 83dfc08ec..23c4e18ad 100644 --- a/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/reference.md +++ b/product-source/hololake-platform/src-tauri/gen/apple/assets/agent-docs/reference.md @@ -15,8 +15,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/resources/agent-docs/all.md b/product-source/hololake-platform/src-tauri/resources/agent-docs/all.md index aefd4fb2d..587e712bd 100644 --- a/product-source/hololake-platform/src-tauri/resources/agent-docs/all.md +++ b/product-source/hololake-platform/src-tauri/resources/agent-docs/all.md @@ -1430,8 +1430,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/resources/agent-docs/pages/reference/contribute.md b/product-source/hololake-platform/src-tauri/resources/agent-docs/pages/reference/contribute.md index d86da933c..9a6b9a678 100644 --- a/product-source/hololake-platform/src-tauri/resources/agent-docs/pages/reference/contribute.md +++ b/product-source/hololake-platform/src-tauri/resources/agent-docs/pages/reference/contribute.md @@ -15,8 +15,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/resources/agent-docs/reference.md b/product-source/hololake-platform/src-tauri/resources/agent-docs/reference.md index 83dfc08ec..23c4e18ad 100644 --- a/product-source/hololake-platform/src-tauri/resources/agent-docs/reference.md +++ b/product-source/hololake-platform/src-tauri/resources/agent-docs/reference.md @@ -15,8 +15,6 @@ HoloLake Era is free and open source, and any kind of help is useful. Pick the p HoloLake Era is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: -- [Codacy](https://www.codacy.com/) -- [CodeScene](https://codescene.com/) - [CircleCI](https://circleci.com/) - [Unblocked](https://getunblocked.com/) diff --git a/product-source/hololake-platform/src-tauri/src/guanghu_living_system.rs b/product-source/hololake-platform/src-tauri/src/guanghu_living_system.rs index 18dcaeb8e..671121e96 100644 --- a/product-source/hololake-platform/src-tauri/src/guanghu_living_system.rs +++ b/product-source/hololake-platform/src-tauri/src/guanghu_living_system.rs @@ -1,10 +1,6 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -#[cfg(desktop)] -use std::path::PathBuf; -#[cfg(desktop)] -use std::sync::{Mutex, OnceLock}; use std::time::Duration; use uuid::Uuid; @@ -12,20 +8,16 @@ const LIGHTHOUSE_MODEL_URL: &str = "http://127.0.0.1:18077/v1/broadcast"; const PERSONA_ID: &str = "ICE-P-ZY001"; const CHANNEL_ID: &str = "HLP-HOLOLAKE-LIVING-SYSTEM"; const MAX_EVENT_ID_BYTES: usize = 96; -#[cfg(desktop)] -const LIGHTHOUSE_SSH_ALIAS: &str = "guanghu-os-bs-sh-005"; -#[cfg(desktop)] -static TUNNEL_LOCK: OnceLock> = OnceLock::new(); const SYSTEM_PROMPT: &str = concat!( "You are the non-conversational HoloLake living-system planner. ", "Return one compact JSON object only. Never chat, explain, use markdown, or call tools. ", - "Keep eventId and intent unchanged. route must equal requestedRoute except world-login may be used as a safe gate. ", + "Keep eventId and intent unchanged. ", + "uiProjection.route and navigationAction.route must equal requestedRoute except world-login may be used as a safe gate. ", "requiredTruth may contain only receiptIds supplied by the event. ", - "scene.depth: overview|focused|immersive. ", - "scene.motion: quiet|responsive|active. ", - "scene.starDensity: sparse|balanced|rich. ", - "scene.connectionEmphasis: contextual|active-route|network. ", + "Return typed uiProjection, navigationAction, capabilityCall, and receiptSchema fields. ", + "capabilityCall must use an exact capability from capabilityRegistry and permissionBoundary. ", + "receiptSchema must remain pending-evidence and cannot claim execution success. ", "Do not infer authorization, server state, identity, or execution success." ); @@ -40,6 +32,12 @@ pub struct LivingSystemEvent { world_open: bool, receipt_ids: Vec, occurred_at: u64, + persona_system: Value, + current_system_state: Value, + knowledge_state: Value, + permission_boundary: Value, + responsibility_boundary: Value, + capability_registry: Vec, } #[derive(Debug, Deserialize)] @@ -69,90 +67,7 @@ pub async fn guanghu_living_system_plan( event: LivingSystemEvent, ) -> Result { let client = living_system_client()?; - match plan_with_lighthouse(&event, LIGHTHOUSE_MODEL_URL, &client).await { - Ok(plan) => Ok(plan), - Err(error) if error.starts_with("guanghu_living_system_transport_failed:") => { - ensure_lighthouse_tunnel().await?; - plan_with_lighthouse(&event, LIGHTHOUSE_MODEL_URL, &client).await - } - Err(error) => Err(error), - } -} - -#[cfg(desktop)] -async fn ensure_lighthouse_tunnel() -> Result<(), String> { - tokio::task::spawn_blocking(|| { - let _guard = TUNNEL_LOCK - .get_or_init(|| Mutex::new(())) - .lock() - .map_err(|_| "guanghu_living_system_tunnel_lock_failed".to_owned())?; - let (config, socket) = lighthouse_tunnel_paths()?; - let check = crate::hidden_command("ssh") - .args(lighthouse_tunnel_check_args(&config, &socket)) - .status(); - if check.is_ok_and(|status| status.success()) { - return Ok(()); - } - let status = crate::hidden_command("ssh") - .args(lighthouse_tunnel_start_args(&config, &socket)) - .status() - .map_err(|error| format!("guanghu_living_system_tunnel_start_failed: {error}"))?; - if !status.success() { - return Err("guanghu_living_system_tunnel_start_failed".to_owned()); - } - Ok(()) - }) - .await - .map_err(|error| format!("guanghu_living_system_tunnel_task_failed: {error}"))? -} - -#[cfg(not(desktop))] -async fn ensure_lighthouse_tunnel() -> Result<(), String> { - Err("guanghu_living_system_tunnel_desktop_required".to_owned()) -} - -#[cfg(desktop)] -fn lighthouse_tunnel_paths() -> Result<(PathBuf, PathBuf), String> { - let ssh_root = dirs::home_dir() - .ok_or_else(|| "guanghu_living_system_home_missing".to_owned())? - .join(".ssh"); - let config = ssh_root.join("guanghu-os-bs-sh-005.conf"); - if !config.is_file() { - return Err("guanghu_living_system_ssh_config_missing".to_owned()); - } - Ok((config, ssh_root.join("guanghu-os-bs-sh-005-tunnel.sock"))) -} - -#[cfg(desktop)] -fn lighthouse_tunnel_check_args(config: &std::path::Path, socket: &std::path::Path) -> Vec { - vec![ - "-F".to_owned(), - config.to_string_lossy().into_owned(), - "-S".to_owned(), - socket.to_string_lossy().into_owned(), - "-O".to_owned(), - "check".to_owned(), - LIGHTHOUSE_SSH_ALIAS.to_owned(), - ] -} - -#[cfg(desktop)] -fn lighthouse_tunnel_start_args(config: &std::path::Path, socket: &std::path::Path) -> Vec { - vec![ - "-F".to_owned(), - config.to_string_lossy().into_owned(), - "-M".to_owned(), - "-S".to_owned(), - socket.to_string_lossy().into_owned(), - "-fN".to_owned(), - "-o".to_owned(), - "ExitOnForwardFailure=yes".to_owned(), - "-L".to_owned(), - "18077:127.0.0.1:8077".to_owned(), - "-L".to_owned(), - "13080:127.0.0.1:3080".to_owned(), - LIGHTHOUSE_SSH_ALIAS.to_owned(), - ] + plan_with_lighthouse(&event, LIGHTHOUSE_MODEL_URL, &client).await } async fn plan_with_lighthouse( @@ -251,6 +166,31 @@ fn validate_event(event: &LivingSystemEvent) -> Result<(), String> { if event.receipt_ids.len() > 32 || event.receipt_ids.iter().any(|value| value.len() > 160) { return Err("guanghu_living_system_receipts_invalid".to_owned()); } + if event.persona_system["personaSystemId"] != PERSONA_ID + || event.persona_system["humanAnchorId"] != "ICE-GL∞" + { + return Err("guanghu_living_system_persona_context_invalid".to_owned()); + } + let runtime_receipt_id = event.current_system_state["runtimeBinding"]["issuedByReceiptId"] + .as_str() + .filter(|value| !value.is_empty()) + .ok_or_else(|| "guanghu_living_system_runtime_binding_invalid".to_owned())?; + if event.current_system_state["runtimeBinding"]["status"] != "verified" + || event.current_system_state["runtimeBinding"]["personaSystemId"] != PERSONA_ID + || !event + .receipt_ids + .iter() + .any(|receipt_id| receipt_id == runtime_receipt_id) + { + return Err("guanghu_living_system_runtime_binding_invalid".to_owned()); + } + if !event.knowledge_state.is_object() + || !event.permission_boundary.is_object() + || !event.responsibility_boundary.is_object() + || event.capability_registry.is_empty() + { + return Err("guanghu_living_system_boundary_context_invalid".to_owned()); + } Ok(()) } @@ -276,8 +216,48 @@ mod tests { requested_route: "fifth-domain".to_owned(), appearance_theme: None, world_open: true, - receipt_ids: vec!["truth-001".to_owned()], + receipt_ids: vec!["truth-001".to_owned(), "runtime-binding-receipt".to_owned()], occurred_at: 1, + persona_system: json!({ + "humanAnchorId": "ICE-GL∞", + "personaSystemId": PERSONA_ID, + "memoryProtocolRoot": "REPO-012", + "modelInstanceId": "model-current" + }), + current_system_state: json!({ + "currentRoute": "world", + "requestedRoute": "fifth-domain", + "worldOpen": true, + "receiptIds": ["truth-001", "runtime-binding-receipt"], + "runtimeBinding": { + "bindingId": "binding-current", + "nodeId": "JD-FD-PRIMARY", + "personaSystemId": PERSONA_ID, + "modelInstanceId": "model-current", + "issuedByReceiptId": "runtime-binding-receipt", + "status": "verified" + } + }), + knowledge_state: json!({ + "selectedLakeId": "lake-current", + "mountedSources": ["REPO-012", "REPO-014"] + }), + permission_boundary: json!({ + "allowedCapabilities": ["navigation.apply"], + "deniedCapabilities": [] + }), + responsibility_boundary: json!({ + "controllerPersonaSystemId": PERSONA_ID, + "humanAnchorId": "ICE-GL∞", + "executorRule": "deterministic-host-only", + "successRule": "evidence-required" + }), + capability_registry: vec![json!({ + "id": "navigation.apply", + "inputType": "NavigationAction", + "outputType": "ReceiptSchema", + "requiresWorldOpen": false + })], } } @@ -363,38 +343,19 @@ mod tests { } #[test] - fn tunnel_arguments_are_fixed_to_the_registered_loopback_routes() { - let config = std::path::Path::new("/registered/ssh.conf"); - let socket = std::path::Path::new("/registered/tunnel.sock"); + fn rejects_server_planning_without_the_current_persona_runtime_binding() { + let mut unbound = event(); + unbound.current_system_state["runtimeBinding"] = Value::Null; assert_eq!( - lighthouse_tunnel_check_args(config, socket), - [ - "-F", - "/registered/ssh.conf", - "-S", - "/registered/tunnel.sock", - "-O", - "check", - LIGHTHOUSE_SSH_ALIAS, - ] + validate_event(&unbound).unwrap_err(), + "guanghu_living_system_runtime_binding_invalid" ); + + let mut wrong_persona = event(); + wrong_persona.persona_system["personaSystemId"] = json!("OTHER-PERSONA"); assert_eq!( - lighthouse_tunnel_start_args(config, socket), - [ - "-F", - "/registered/ssh.conf", - "-M", - "-S", - "/registered/tunnel.sock", - "-fN", - "-o", - "ExitOnForwardFailure=yes", - "-L", - "18077:127.0.0.1:8077", - "-L", - "13080:127.0.0.1:3080", - LIGHTHOUSE_SSH_ALIAS, - ] + validate_event(&wrong_persona).unwrap_err(), + "guanghu_living_system_persona_context_invalid" ); } diff --git a/product-source/hololake-platform/src/assets/sponsors/codacy-dark.svg b/product-source/hololake-platform/src/assets/sponsors/codacy-dark.svg deleted file mode 100644 index 882c2c5fa..000000000 --- a/product-source/hololake-platform/src/assets/sponsors/codacy-dark.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/product-source/hololake-platform/src/assets/sponsors/codacy-light.svg b/product-source/hololake-platform/src/assets/sponsors/codacy-light.svg deleted file mode 100644 index 6b74afcea..000000000 --- a/product-source/hololake-platform/src/assets/sponsors/codacy-light.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/product-source/hololake-platform/src/assets/sponsors/codescene-dark.svg b/product-source/hololake-platform/src/assets/sponsors/codescene-dark.svg deleted file mode 100644 index 2cc8968d9..000000000 --- a/product-source/hololake-platform/src/assets/sponsors/codescene-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/product-source/hololake-platform/src/assets/sponsors/codescene-light.svg b/product-source/hololake-platform/src/assets/sponsors/codescene-light.svg deleted file mode 100644 index 56dcb5927..000000000 --- a/product-source/hololake-platform/src/assets/sponsors/codescene-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/product-source/hololake-platform/src/components/FeedbackDialog.test.tsx b/product-source/hololake-platform/src/components/FeedbackDialog.test.tsx index 8a6af86a4..12063bc66 100644 --- a/product-source/hololake-platform/src/components/FeedbackDialog.test.tsx +++ b/product-source/hololake-platform/src/components/FeedbackDialog.test.tsx @@ -3,8 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { FeedbackDialog } from './FeedbackDialog' import { CIRCLECI_HOME_URL, - CODACY_HOME_URL, - CODESCENE_HOME_URL, REFACTORING_HOME_URL, TOLARIA_GITHUB_CONTRIBUTING_URL, TOLARIA_GITHUB_DISCUSSIONS_URL, @@ -48,8 +46,6 @@ describe('FeedbackDialog', () => { expect(screen.getByText('Report a bug')).toBeInTheDocument() expect(screen.getByText(/Refactoring is my newsletter and community/i)).toBeInTheDocument() expect(screen.getByText(/HoloLake Era is supported by a panel of tools/i)).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Open Codacy' })).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Open CodeScene' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'Open CircleCI' })).toBeInTheDocument() expect(screen.getByRole('button', { name: 'Open Unblocked' })).toBeInTheDocument() expect(screen.getByText('Search on the board first, upvote existing ideas, and create new posts when genuinely new!')).toBeInTheDocument() @@ -81,8 +77,6 @@ describe('FeedbackDialog', () => { render() fireEvent.click(screen.getByRole('button', { name: 'Check out Refactoring' })) - fireEvent.click(screen.getByRole('button', { name: 'Open Codacy' })) - fireEvent.click(screen.getByRole('button', { name: 'Open CodeScene' })) fireEvent.click(screen.getByRole('button', { name: 'Open CircleCI' })) fireEvent.click(screen.getByRole('button', { name: 'Open Unblocked' })) fireEvent.click(screen.getByRole('button', { name: 'Open Product Board' })) @@ -92,15 +86,13 @@ describe('FeedbackDialog', () => { fireEvent.click(screen.getByRole('button', { name: 'Open GitHub Issues' })) await waitFor(() => expect(openExternalUrl).toHaveBeenNthCalledWith(1, REFACTORING_HOME_URL)) - expect(openExternalUrl).toHaveBeenNthCalledWith(2, CODACY_HOME_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(3, CODESCENE_HOME_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(4, CIRCLECI_HOME_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(5, UNBLOCKED_HOME_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(6, TOLARIA_PRODUCT_BOARD_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(7, TOLARIA_GITHUB_DISCUSSIONS_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(8, TOLARIA_GITHUB_PULL_REQUESTS_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(9, TOLARIA_GITHUB_CONTRIBUTING_URL) - expect(openExternalUrl).toHaveBeenNthCalledWith(10, TOLARIA_GITHUB_ISSUES_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(2, CIRCLECI_HOME_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(3, UNBLOCKED_HOME_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(4, TOLARIA_PRODUCT_BOARD_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(5, TOLARIA_GITHUB_DISCUSSIONS_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(6, TOLARIA_GITHUB_PULL_REQUESTS_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(7, TOLARIA_GITHUB_CONTRIBUTING_URL) + expect(openExternalUrl).toHaveBeenNthCalledWith(8, TOLARIA_GITHUB_ISSUES_URL) expect(onClose).not.toHaveBeenCalled() expect(screen.getByTestId('feedback-dialog')).toBeInTheDocument() }) diff --git a/product-source/hololake-platform/src/components/FeedbackDialog.tsx b/product-source/hololake-platform/src/components/FeedbackDialog.tsx index 109003f97..e67c8a22a 100644 --- a/product-source/hololake-platform/src/components/FeedbackDialog.tsx +++ b/product-source/hololake-platform/src/components/FeedbackDialog.tsx @@ -2,10 +2,6 @@ import { ArrowUpRight, Bug, Chats as MessagesSquare, Check, Copy, GitPullRequest import { type ReactNode, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import circleCiDarkLogo from '@/assets/sponsors/circleci-dark.svg' import circleCiLightLogo from '@/assets/sponsors/circleci-light.svg' -import codacyDarkLogo from '@/assets/sponsors/codacy-dark.svg' -import codacyLightLogo from '@/assets/sponsors/codacy-light.svg' -import codeSceneDarkLogo from '@/assets/sponsors/codescene-dark.svg' -import codeSceneLightLogo from '@/assets/sponsors/codescene-light.svg' import unblockedDarkLogo from '@/assets/sponsors/unblocked-dark.svg' import unblockedLightLogo from '@/assets/sponsors/unblocked-light.svg' import { Button } from '@/components/ui/button' @@ -26,8 +22,6 @@ import { } from '@/components/ui/dialog' import { CIRCLECI_HOME_URL, - CODACY_HOME_URL, - CODESCENE_HOME_URL, REFACTORING_HOME_URL, TOLARIA_GITHUB_CONTRIBUTING_URL, TOLARIA_GITHUB_DISCUSSIONS_URL, @@ -129,18 +123,6 @@ const NEWSLETTER_PATH = { } satisfies ContributionPath const SPONSOR_LOGOS: SponsorLogo[] = [ - { - name: 'Codacy', - url: CODACY_HOME_URL, - darkLogo: codacyDarkLogo, - lightLogo: codacyLightLogo, - }, - { - name: 'CodeScene', - url: CODESCENE_HOME_URL, - darkLogo: codeSceneDarkLogo, - lightLogo: codeSceneLightLogo, - }, { name: 'CircleCI', url: CIRCLECI_HOME_URL, diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx index 316d6a7f9..80b2b1c86 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.test.tsx @@ -94,6 +94,51 @@ const worldLogin = ( ...overrides, }) +const livingPlan = ({ + eventId = 'model-event', + intent = 'navigate', + route = 'fifth-domain', + motion = 'active', +}: { + eventId?: string + intent?: 'navigate' | 'open-knowledge' | 'open-agent-workspace' | 'open-local-workspace' | 'apply-theme' + route?: 'world' | 'fifth-domain' + motion?: 'quiet' | 'responsive' | 'active' +} = {}) => ({ + version: 1 as const, + eventId, + intent, + planId: 'model-plan', + requiredTruth: [], + uiProjection: { + route, + stateLabel: 'model-proposed' as const, + scene: { + depth: route === 'world' ? 'overview' as const : 'immersive' as const, + motion, + starDensity: 'rich' as const, + connectionEmphasis: 'network' as const, + }, + }, + navigationAction: { type: 'navigate' as const, route }, + capabilityCall: intent === 'navigate' + ? null + : { + type: 'capability' as const, + capabilityId: { + 'open-knowledge': 'knowledge.open', + 'open-agent-workspace': 'agent.workspace.open', + 'open-local-workspace': 'local.workspace.open', + 'apply-theme': 'appearance.apply', + }[intent], + input: {}, + }, + receiptSchema: { + outcome: 'pending-evidence' as const, + requiredEvidence: [], + }, +}) + describe('HoloLakeHome', () => { beforeEach(() => { vi.clearAllMocks() @@ -116,20 +161,7 @@ describe('HoloLakeHome', () => { }) planGuanghuLivingSystemMock.mockResolvedValue({ source: 'model', - plan: { - version: 1, - eventId: 'model-event', - intent: 'navigate', - planId: 'model-plan', - route: 'fifth-domain', - requiredTruth: [], - scene: { - depth: 'immersive', - motion: 'active', - starDensity: 'rich', - connectionEmphasis: 'network', - }, - }, + plan: livingPlan(), }) }) @@ -243,18 +275,13 @@ describe('HoloLakeHome', () => { source: 'server', serverReceiptId: 'GMRP-HOST-ACTION-001', plan: { - version: 1, - eventId: event.eventId, - intent: event.intent, - planId: 'host-action-plan', - route: event.requestedRoute, - requiredTruth: [], - scene: { - depth: 'overview', + ...livingPlan({ + eventId: event.eventId, + intent: event.intent, + route: event.requestedRoute, motion: 'responsive', - starDensity: 'balanced', - connectionEmphasis: 'contextual', - }, + }), + planId: 'host-action-plan', }, })) @@ -292,18 +319,13 @@ describe('HoloLakeHome', () => { source: 'server', serverReceiptId: 'GMRP-THEME-001', plan: { - version: 1, - eventId: event.eventId, - intent: event.intent, - planId: 'theme-plan', - route: event.requestedRoute, - requiredTruth: [], - scene: { - depth: 'overview', + ...livingPlan({ + eventId: event.eventId, + intent: event.intent, + route: event.requestedRoute, motion: 'active', - starDensity: 'rich', - connectionEmphasis: 'network', - }, + }), + planId: 'theme-plan', }, })) diff --git a/product-source/hololake-platform/src/components/HoloLakeHome.tsx b/product-source/hololake-platform/src/components/HoloLakeHome.tsx index f437644a4..eca907e4f 100644 --- a/product-source/hololake-platform/src/components/HoloLakeHome.tsx +++ b/product-source/hololake-platform/src/components/HoloLakeHome.tsx @@ -101,7 +101,7 @@ export function HoloLakeHome({ })) )) const [livingKernel, setLivingKernel] = useState<'server' | 'model' | 'fallback' | 'planning'>('fallback') - const [livingReceiptId, setLivingReceiptId] = useState('local-execution:system-start') + const [livingReceiptId, setLivingReceiptId] = useState('local-state:system-start') const [livingServerReceiptId, setLivingServerReceiptId] = useState() const [architectureOpen, setArchitectureOpen] = useState(false) const [releaseNotesOpen, setReleaseNotesOpen] = useState(false) @@ -117,25 +117,47 @@ export function HoloLakeHome({ const worldOpen = login.state.phase === 'online' const fifthDomainOpen = worldOpen && route !== 'world' const worldRouterState = router.state + const activeRouterReceipt = worldRouterState.status === 'online' + && worldRouterState.latestReceipt?.state === 'online' + ? worldRouterState.latestReceipt + : null const t = (key: TranslationKey) => translate(locale, key) - const executeLivingPlan = useCallback(( + const applyLivingPlan = useCallback(( plan: GuanghuLivingSystemPlan, source: 'server' | 'model' | 'fallback', serverReceiptId?: string, ) => { - const receipt = createLivingSystemExecutionReceipt({ plan, source }) setLivingPlan(plan) setLivingKernel(source) - setLivingReceiptId(receipt.receiptId) setLivingServerReceiptId(serverReceiptId) - setRoute(plan.route) + setRoute(plan.navigationAction.route) + }, []) + + const recordLivingExecution = useCallback(( + plan: GuanghuLivingSystemPlan, + source: 'server' | 'model' | 'fallback', + outcome: 'executed' | 'failed', + evidence: string[], + error?: string, + serverReceiptId?: string, + ) => { + const receipt = createLivingSystemExecutionReceipt({ + plan, + source, + outcome, + evidence, + error, + }) + setLivingReceiptId(receipt.receiptId) trackEvent('guanghu_living_system_receipt', { eventId: receipt.eventId, + outcome: receipt.outcome, planId: receipt.planId, receiptId: receipt.receiptId, route: receipt.route, source: receipt.source, + evidence: receipt.evidence.join(','), ...(serverReceiptId ? { serverReceiptId } : {}), }) }, []) @@ -160,29 +182,49 @@ export function HoloLakeHome({ appearanceTheme, worldOpen, }) - const executeAcceptedPlan = ( + const executeAcceptedPlan = async ( plan: GuanghuLivingSystemPlan, source: 'server' | 'model' | 'fallback', serverReceiptId?: string, ) => { - executeLivingPlan(plan, source, serverReceiptId) - if (plan.intent === intent && plan.route === nextRoute) { - void onAccepted?.() + applyLivingPlan(plan, source, serverReceiptId) + const routeAccepted = plan.intent === intent && plan.navigationAction.route === nextRoute + try { + if (routeAccepted && onAccepted) { + await onAccepted() + } + const evidence = [ + `ui.route:${plan.navigationAction.route}`, + ...(routeAccepted && onAccepted && plan.capabilityCall + ? [`capability.${plan.capabilityCall.capabilityId}.result`] + : []), + ] + recordLivingExecution(plan, source, 'executed', evidence, undefined, serverReceiptId) + } catch (error) { + recordLivingExecution( + plan, + source, + 'failed', + [`ui.route:${plan.navigationAction.route}`], + error instanceof Error ? error.message : 'host_action_failed', + serverReceiptId, + ) } } if (!isTauri() && !livingSystemTarget) { - executeAcceptedPlan(createDeterministicLivingSystemPlan(event), 'fallback') + void executeAcceptedPlan(createDeterministicLivingSystemPlan(event), 'fallback') return } setLivingKernel('planning') void planGuanghuLivingSystem({ event, target: livingSystemTarget }).then(result => { if (livingRequestSequence.current !== sequence) return - executeAcceptedPlan(result.plan, result.source, result.serverReceiptId) + void executeAcceptedPlan(result.plan, result.source, result.serverReceiptId) }) }, [ - executeLivingPlan, + applyLivingPlan, livingSystemTarget, login.state.workorderId, + recordLivingExecution, route, worldOpen, worldRouterState.latestReceipt?.receipt_id, @@ -259,7 +301,7 @@ export function HoloLakeHome({ return ( navigate(worldOpen ? 'fifth-domain' : 'world-login')} onOpenLibrary={openArchitecture} onOpenReceipt={() => navigate('servers')} @@ -376,17 +418,21 @@ export function HoloLakeHome({
- 已进入光湖世界第五域 · JD-FD-PRIMARY + + {activeRouterReceipt ? '第五域连接已回执' : '光湖身份已授权'} + {activeRouterReceipt ? `第五域 · ${activeRouterReceipt.node_id}` : '等待真实节点回执'} +
- 当前世界节点 - JD-FD-PRIMARY -

第五域国内主控节点

+ {activeRouterReceipt ? '当前世界节点' : '登记目标节点'} + {activeRouterReceipt?.node_id ?? 'JD-FD-PRIMARY'} +

{activeRouterReceipt ? '第五域连接已由节点回执证明' : '尚未把身份授权误报为节点连接'}

身份
冰朔
-
世界状态
已打开
+
身份会话
{worldOpen ? '已授权' : '未授权'}
+
节点连接
{activeRouterReceipt ? '已回执' : '未回执'}
公开发现入口
{fifthDomainConnection.status === 'connected' ? '可用' : fifthDomainConnection.status === 'error' ? '失败' : '检查中'}
{worldOpen && route !== 'world-login' ? <>
-
已进入光湖世界{fifthDomainOpen ? '第五域 · JD-FD-PRIMARY' : '世界入口 · 等待域跳转'}
+
+ + + {activeRouterReceipt ? '第五域连接已回执' : '光湖身份已授权'} + + {activeRouterReceipt + ? `第五域 · ${activeRouterReceipt.node_id}` + : fifthDomainOpen + ? '域内界面已打开 · 节点未回执' + : '世界入口 · 等待域跳转'} + + +
{fifthDomainOpen && onOpenAiWorkspace ? diff --git a/product-source/hololake-platform/src/constants/feedback.ts b/product-source/hololake-platform/src/constants/feedback.ts index 67972cdac..f437a299f 100644 --- a/product-source/hololake-platform/src/constants/feedback.ts +++ b/product-source/hololake-platform/src/constants/feedback.ts @@ -1,6 +1,4 @@ export const REFACTORING_HOME_URL = 'https://refactoring.fm/' -export const CODACY_HOME_URL = 'https://www.codacy.com/' -export const CODESCENE_HOME_URL = 'https://codescene.com/' export const CIRCLECI_HOME_URL = 'https://circleci.com/' export const UNBLOCKED_HOME_URL = 'https://getunblocked.com/' export const HOLOLAKE_REPOSITORY_URL = 'https://guanghulab.com/code/bingshuo/hololake-system-architecture' diff --git a/product-source/hololake-platform/src/lib/guanghuLivingSystem.test.ts b/product-source/hololake-platform/src/lib/guanghuLivingSystem.test.ts index 90d7c98d8..329d963cb 100644 --- a/product-source/hololake-platform/src/lib/guanghuLivingSystem.test.ts +++ b/product-source/hololake-platform/src/lib/guanghuLivingSystem.test.ts @@ -8,6 +8,61 @@ import { } from './guanghuLivingSystem' describe('Guanghu living system contract', () => { + it('binds every event to the persona system, knowledge state, boundaries, and capability registry', () => { + const event = createLivingSystemEvent({ + currentRoute: 'world', + eventId: 'event-context', + intent: 'open-knowledge', + receiptIds: ['truth-001'], + requestedRoute: 'world', + worldOpen: true, + modelInstanceId: 'model-instance-current', + knowledgeState: { + selectedLakeId: 'lake-current', + mountedSources: ['REPO-012', 'REPO-014'], + }, + }) + + expect(event.personaSystem).toEqual({ + humanAnchorId: 'ICE-GL∞', + personaSystemId: 'ICE-P-ZY001', + memoryProtocolRoot: 'REPO-012', + modelInstanceId: 'model-instance-current', + }) + expect(event.knowledgeState).toEqual({ + selectedLakeId: 'lake-current', + mountedSources: ['REPO-012', 'REPO-014'], + }) + expect(event.permissionBoundary.allowedCapabilities).toContain('knowledge.open') + expect(event.responsibilityBoundary.controllerPersonaSystemId).toBe('ICE-P-ZY001') + expect(event.capabilityRegistry).toContainEqual(expect.objectContaining({ + id: 'knowledge.open', + outputType: 'CapabilityCall', + })) + }) + + it('drops a claimed runtime binding when its issuing receipt is absent', () => { + const event = createLivingSystemEvent({ + currentRoute: 'world', + eventId: 'event-unverified-runtime', + intent: 'navigate', + receiptIds: [], + requestedRoute: 'world', + worldOpen: true, + runtimeBinding: { + bindingId: 'binding-unverified', + nodeId: 'JD-FD-PRIMARY', + personaSystemId: 'ICE-P-ZY001', + modelInstanceId: 'model-unverified', + issuedByReceiptId: 'missing-receipt', + status: 'verified', + }, + }) + + expect(event.currentSystemState.runtimeBinding).toBeNull() + expect(event.personaSystem.modelInstanceId).toBeNull() + }) + it('accepts a model-native navigation plan with a bounded visual scene', () => { const event = createLivingSystemEvent({ currentRoute: 'world', @@ -23,14 +78,20 @@ describe('Guanghu living system contract', () => { eventId: 'event-001', intent: 'navigate', planId: 'plan-001', - route: 'fifth-domain', requiredTruth: ['receipt-001'], - scene: { - depth: 'immersive', - motion: 'responsive', - starDensity: 'rich', - connectionEmphasis: 'active-route', + uiProjection: { + route: 'fifth-domain', + stateLabel: 'model-proposed', + scene: { + depth: 'immersive', + motion: 'responsive', + starDensity: 'rich', + connectionEmphasis: 'active-route', + }, }, + navigationAction: { type: 'navigate', route: 'fifth-domain' }, + capabilityCall: null, + receiptSchema: { outcome: 'pending-evidence', requiredEvidence: ['ui.route.readback'] }, })) expect(validateLivingSystemPlan(event, plan)).toEqual({ @@ -54,11 +115,17 @@ describe('Guanghu living system contract', () => { eventId: 'event-002', intent: 'navigate', planId: 'plan-002', - route: 'fifth-domain', requiredTruth: ['invented-receipt'], - scene: { - depth: 'focused', motion: 'quiet', starDensity: 'balanced', connectionEmphasis: 'contextual', + uiProjection: { + route: 'fifth-domain', + stateLabel: 'model-proposed', + scene: { + depth: 'focused', motion: 'quiet', starDensity: 'balanced', connectionEmphasis: 'contextual', + }, }, + navigationAction: { type: 'navigate', route: 'fifth-domain' }, + capabilityCall: null, + receiptSchema: { outcome: 'pending-evidence', requiredEvidence: [] }, })) expect(validateLivingSystemPlan(event, plan)).toEqual({ @@ -79,8 +146,11 @@ describe('Guanghu living system contract', () => { }) expect(createDeterministicLivingSystemPlan(event)).toMatchObject({ - route: 'world-login', - scene: { depth: 'focused', motion: 'responsive' }, + uiProjection: { + route: 'world-login', + scene: { depth: 'focused', motion: 'responsive' }, + }, + navigationAction: { route: 'world-login' }, }) }) @@ -99,14 +169,24 @@ describe('Guanghu living system contract', () => { eventId: event.eventId, intent: 'open-agent-workspace', planId: 'substituted-action', - route: 'world', requiredTruth: [], - scene: { - depth: 'overview', - motion: 'responsive', - starDensity: 'balanced', - connectionEmphasis: 'contextual', + uiProjection: { + route: 'world', + stateLabel: 'model-proposed', + scene: { + depth: 'overview', + motion: 'responsive', + starDensity: 'balanced', + connectionEmphasis: 'contextual', + }, }, + navigationAction: { type: 'navigate', route: 'world' }, + capabilityCall: { + type: 'capability', + capabilityId: 'agent.workspace.open', + input: {}, + }, + receiptSchema: { outcome: 'pending-evidence', requiredEvidence: [] }, })) expect(validateLivingSystemPlan(event, plan)).toEqual({ @@ -115,9 +195,76 @@ describe('Guanghu living system contract', () => { }) }) + it('rejects route substitution, closed-world projection, and capability substitution', () => { + const openEvent = createLivingSystemEvent({ + currentRoute: 'world', + eventId: 'event-route-boundary', + intent: 'navigate', + requestedRoute: 'fifth-domain', + worldOpen: true, + receiptIds: [], + now: 1, + }) + const openPlan = createDeterministicLivingSystemPlan(openEvent) + expect(validateLivingSystemPlan(openEvent, { + ...openPlan, + navigationAction: { type: 'navigate', route: 'world' }, + })).toEqual({ accepted: false, reason: 'route_mismatch' }) + + const closedEvent = createLivingSystemEvent({ + currentRoute: 'world', + eventId: 'event-world-boundary', + intent: 'navigate', + requestedRoute: 'fifth-domain', + worldOpen: false, + receiptIds: [], + now: 1, + }) + const closedPlan = createDeterministicLivingSystemPlan(closedEvent) + const protectedPlan = { + ...closedPlan, + uiProjection: { ...closedPlan.uiProjection, route: 'fifth-domain' as const }, + navigationAction: { type: 'navigate' as const, route: 'fifth-domain' as const }, + } + expect(validateLivingSystemPlan(closedEvent, protectedPlan)).toEqual({ + accepted: false, + reason: 'world_closed', + }) + + const capabilityEvent = createLivingSystemEvent({ + currentRoute: 'world', + eventId: 'event-capability-boundary', + intent: 'open-knowledge', + requestedRoute: 'world', + worldOpen: true, + receiptIds: [], + now: 1, + }) + const capabilityPlan = createDeterministicLivingSystemPlan(capabilityEvent) + expect(validateLivingSystemPlan(capabilityEvent, { + ...capabilityPlan, + capabilityCall: { + type: 'capability', + capabilityId: 'agent.workspace.open', + input: {}, + }, + })).toEqual({ accepted: false, reason: 'capability_mismatch' }) + }) + it('rejects chatty or structurally invalid model output', () => { expect(parseLivingSystemPlan('我来帮你进入第五域')).toBeNull() expect(parseLivingSystemPlan('{"route":"fifth-domain"}')).toBeNull() + expect(parseLivingSystemPlan(JSON.stringify({ + version: 1, + eventId: 'event-001', + intent: 'navigate', + planId: 'legacy-theme-adapter', + route: 'fifth-domain', + requiredTruth: [], + scene: { + depth: 'immersive', motion: 'active', starDensity: 'rich', connectionEmphasis: 'network', + }, + }))).toBeNull() }) it('binds a local executor receipt to the accepted event and plan', () => { @@ -131,16 +278,43 @@ describe('Guanghu living system contract', () => { now: 1, })) - expect(createLivingSystemExecutionReceipt({ plan, source: 'fallback', now: 2 })).toEqual({ + expect(createLivingSystemExecutionReceipt({ + plan, + source: 'fallback', + outcome: 'executed', + evidence: ['ui.route:zero-core'], + now: 2, + })).toEqual({ version: 1, - receiptId: 'local-execution:fallback-event-004', + receiptId: 'local-execution:fallback-event-004:executed', eventId: 'event-004', intent: 'navigate', planId: 'fallback-event-004', route: 'zero-core', source: 'fallback', outcome: 'executed', - executedAt: 2, + evidence: ['ui.route:zero-core'], + completedAt: 2, }) }) + + it('refuses to create a successful receipt before real evidence exists', () => { + const plan = createDeterministicLivingSystemPlan(createLivingSystemEvent({ + currentRoute: 'world', + intent: 'navigate', + requestedRoute: 'zero-core', + worldOpen: true, + receiptIds: [], + eventId: 'event-no-evidence', + now: 1, + })) + + expect(() => createLivingSystemExecutionReceipt({ + plan, + source: 'fallback', + outcome: 'executed', + evidence: [], + now: 2, + })).toThrow('guanghu_living_system_success_evidence_required') + }) }) diff --git a/product-source/hololake-platform/src/lib/guanghuLivingSystem.ts b/product-source/hololake-platform/src/lib/guanghuLivingSystem.ts index 33fd727e8..d08d59068 100644 --- a/product-source/hololake-platform/src/lib/guanghuLivingSystem.ts +++ b/product-source/hololake-platform/src/lib/guanghuLivingSystem.ts @@ -34,6 +34,61 @@ export type GuanghuLivingScene = { connectionEmphasis: LivingSceneConnectionEmphasis } +export type GuanghuPersonaSystemContext = { + humanAnchorId: 'ICE-GL∞' + personaSystemId: 'ICE-P-ZY001' + memoryProtocolRoot: 'REPO-012' + modelInstanceId: string | null +} + +export type GuanghuRuntimeBinding = { + bindingId: string + nodeId: string + personaSystemId: 'ICE-P-ZY001' + modelInstanceId: string + issuedByReceiptId: string + status: 'verified' +} + +export type GuanghuKnowledgeState = { + selectedLakeId: string | null + mountedSources: string[] +} + +export type GuanghuCapabilityId = + | 'navigation.apply' + | 'knowledge.open' + | 'agent.workspace.open' + | 'local.workspace.open' + | 'appearance.apply' + +export type GuanghuCapabilityRegistration = { + id: GuanghuCapabilityId + inputType: 'NavigationAction' | 'CapabilityCall' + outputType: 'ReceiptSchema' | 'CapabilityCall' + requiresWorldOpen: boolean +} + +export type GuanghuPermissionBoundary = { + allowedCapabilities: GuanghuCapabilityId[] + deniedCapabilities: string[] +} + +export type GuanghuResponsibilityBoundary = { + controllerPersonaSystemId: 'ICE-P-ZY001' + humanAnchorId: 'ICE-GL∞' + executorRule: 'deterministic-host-only' + successRule: 'evidence-required' +} + +export type GuanghuCurrentSystemState = { + currentRoute: GuanghuChannelRoute + requestedRoute: GuanghuChannelRoute + worldOpen: boolean + receiptIds: string[] + runtimeBinding: GuanghuRuntimeBinding | null +} + export type GuanghuLivingSystemEvent = { eventId: string intent: GuanghuLivingIntent @@ -43,6 +98,34 @@ export type GuanghuLivingSystemEvent = { worldOpen: boolean receiptIds: string[] occurredAt: number + personaSystem: GuanghuPersonaSystemContext + currentSystemState: GuanghuCurrentSystemState + knowledgeState: GuanghuKnowledgeState + permissionBoundary: GuanghuPermissionBoundary + responsibilityBoundary: GuanghuResponsibilityBoundary + capabilityRegistry: GuanghuCapabilityRegistration[] +} + +export type GuanghuUIProjection = { + route: GuanghuChannelRoute + scene: GuanghuLivingScene + stateLabel: 'local-safe' | 'model-proposed' | 'server-bound' +} + +export type GuanghuNavigationAction = { + type: 'navigate' + route: GuanghuChannelRoute +} + +export type GuanghuCapabilityCall = { + type: 'capability' + capabilityId: GuanghuCapabilityId + input: Record +} + +export type GuanghuReceiptSchema = { + outcome: 'pending-evidence' + requiredEvidence: string[] } export type GuanghuLivingSystemPlan = { @@ -50,9 +133,11 @@ export type GuanghuLivingSystemPlan = { eventId: string intent: GuanghuLivingIntent planId: string - route: GuanghuChannelRoute requiredTruth: string[] - scene: GuanghuLivingScene + uiProjection: GuanghuUIProjection + navigationAction: GuanghuNavigationAction + capabilityCall: GuanghuCapabilityCall | null + receiptSchema: GuanghuReceiptSchema } export type GuanghuLivingSystemExecutionReceipt = { @@ -63,17 +148,40 @@ export type GuanghuLivingSystemExecutionReceipt = { planId: string route: GuanghuChannelRoute source: 'server' | 'model' | 'fallback' - outcome: 'executed' - executedAt: number + outcome: 'executed' | 'failed' + evidence: string[] + completedAt: number + error?: string } -type CreateLivingSystemEventInput = Omit & { +type CreateLivingSystemEventInput = Omit< + GuanghuLivingSystemEvent, + | 'occurredAt' + | 'personaSystem' + | 'currentSystemState' + | 'knowledgeState' + | 'permissionBoundary' + | 'responsibilityBoundary' + | 'capabilityRegistry' +> & { now?: number + modelInstanceId?: string | null + runtimeBinding?: GuanghuRuntimeBinding | null + knowledgeState?: GuanghuKnowledgeState } export type LivingSystemPlanValidation = | { accepted: true; plan: GuanghuLivingSystemPlan } - | { accepted: false; reason: 'event_mismatch' | 'intent_mismatch' | 'route_mismatch' | 'unverified_truth' | 'world_closed' } + | { + accepted: false + reason: + | 'event_mismatch' + | 'intent_mismatch' + | 'route_mismatch' + | 'unverified_truth' + | 'world_closed' + | 'capability_mismatch' + } const PROTECTED_ROUTES = new Set([ 'fifth-domain', @@ -90,6 +198,39 @@ const SCENE_MOTIONS = new Set(['quiet', 'responsive', 'active const STAR_DENSITIES = new Set(['sparse', 'balanced', 'rich']) const CONNECTION_EMPHASES = new Set(['contextual', 'active-route', 'network']) const LIVING_INTENTS = new Set(GUANGHU_LIVING_INTENTS) +const CAPABILITY_FOR_INTENT: Record = { + navigate: 'navigation.apply', + 'open-knowledge': 'knowledge.open', + 'open-agent-workspace': 'agent.workspace.open', + 'open-local-workspace': 'local.workspace.open', + 'apply-theme': 'appearance.apply', +} +const CAPABILITY_REGISTRY: GuanghuCapabilityRegistration[] = [ + { id: 'navigation.apply', inputType: 'NavigationAction', outputType: 'ReceiptSchema', requiresWorldOpen: false }, + { id: 'knowledge.open', inputType: 'CapabilityCall', outputType: 'CapabilityCall', requiresWorldOpen: false }, + { id: 'agent.workspace.open', inputType: 'CapabilityCall', outputType: 'CapabilityCall', requiresWorldOpen: false }, + { id: 'local.workspace.open', inputType: 'CapabilityCall', outputType: 'CapabilityCall', requiresWorldOpen: false }, + { id: 'appearance.apply', inputType: 'CapabilityCall', outputType: 'CapabilityCall', requiresWorldOpen: false }, +] + +function verifiedRuntimeBinding( + binding: GuanghuRuntimeBinding | null | undefined, + receiptIds: string[], +): GuanghuRuntimeBinding | null { + if ( + !binding + || binding.status !== 'verified' + || binding.personaSystemId !== 'ICE-P-ZY001' + || binding.bindingId.length === 0 + || binding.nodeId.length === 0 + || binding.modelInstanceId.length === 0 + || binding.issuedByReceiptId.length === 0 + || !receiptIds.includes(binding.issuedByReceiptId) + ) { + return null + } + return { ...binding } +} function isStringArray(value: unknown): value is string[] { return Array.isArray(value) && value.every(item => typeof item === 'string') @@ -99,26 +240,58 @@ function isRoute(value: unknown): value is GuanghuChannelRoute { return typeof value === 'string' && (GUANGHU_CHANNEL_ROUTES as readonly string[]).includes(value) } +function isLivingScene(value: unknown): value is GuanghuLivingScene { + if (!value || typeof value !== 'object') return false + const scene = value as Partial + return SCENE_DEPTHS.has(scene.depth!) + && SCENE_MOTIONS.has(scene.motion!) + && STAR_DENSITIES.has(scene.starDensity!) + && CONNECTION_EMPHASES.has(scene.connectionEmphasis!) +} + +function isCapabilityCall(value: unknown): value is GuanghuCapabilityCall | null { + if (value === null) return true + if (!value || typeof value !== 'object') return false + const call = value as Partial + return call.type === 'capability' + && Object.values(CAPABILITY_FOR_INTENT).includes(call.capabilityId!) + && Boolean(call.input) + && typeof call.input === 'object' + && !Array.isArray(call.input) +} + function isLivingSystemPlan(value: unknown): value is GuanghuLivingSystemPlan { if (!value || typeof value !== 'object') return false const candidate = value as Partial - const scene = candidate.scene + const projection = candidate.uiProjection + const navigation = candidate.navigationAction + const receipt = candidate.receiptSchema return candidate.version === GUANGHU_LIVING_SYSTEM_VERSION && typeof candidate.eventId === 'string' && candidate.eventId.length > 0 && LIVING_INTENTS.has(candidate.intent!) && typeof candidate.planId === 'string' && candidate.planId.length > 0 - && isRoute(candidate.route) && isStringArray(candidate.requiredTruth) - && Boolean(scene) - && SCENE_DEPTHS.has(scene!.depth) - && SCENE_MOTIONS.has(scene!.motion) - && STAR_DENSITIES.has(scene!.starDensity) - && CONNECTION_EMPHASES.has(scene!.connectionEmphasis) + && Boolean(projection) + && isRoute(projection!.route) + && isLivingScene(projection!.scene) + && ['local-safe', 'model-proposed', 'server-bound'].includes(projection!.stateLabel) + && Boolean(navigation) + && navigation!.type === 'navigate' + && isRoute(navigation!.route) + && isCapabilityCall(candidate.capabilityCall) + && Boolean(receipt) + && receipt!.outcome === 'pending-evidence' + && isStringArray(receipt!.requiredEvidence) } export function createLivingSystemEvent(input: CreateLivingSystemEventInput): GuanghuLivingSystemEvent { + const runtimeBinding = verifiedRuntimeBinding(input.runtimeBinding, input.receiptIds) + const allowedCapabilities = CAPABILITY_REGISTRY + .filter(capability => !capability.requiresWorldOpen || input.worldOpen) + .map(capability => capability.id) + const receiptIds = [...input.receiptIds] return { eventId: input.eventId, intent: input.intent, @@ -126,8 +299,36 @@ export function createLivingSystemEvent(input: CreateLivingSystemEventInput): Gu requestedRoute: input.requestedRoute, ...(input.appearanceTheme ? { appearanceTheme: input.appearanceTheme } : {}), worldOpen: input.worldOpen, - receiptIds: [...input.receiptIds], + receiptIds, occurredAt: input.now ?? Date.now(), + personaSystem: { + humanAnchorId: 'ICE-GL∞', + personaSystemId: 'ICE-P-ZY001', + memoryProtocolRoot: 'REPO-012', + modelInstanceId: input.modelInstanceId ?? runtimeBinding?.modelInstanceId ?? null, + }, + currentSystemState: { + currentRoute: input.currentRoute, + requestedRoute: input.requestedRoute, + worldOpen: input.worldOpen, + receiptIds, + runtimeBinding, + }, + knowledgeState: input.knowledgeState ?? { + selectedLakeId: null, + mountedSources: [], + }, + permissionBoundary: { + allowedCapabilities, + deniedCapabilities: [], + }, + responsibilityBoundary: { + controllerPersonaSystemId: 'ICE-P-ZY001', + humanAnchorId: 'ICE-GL∞', + executorRule: 'deterministic-host-only', + successRule: 'evidence-required', + }, + capabilityRegistry: CAPABILITY_REGISTRY.map(capability => ({ ...capability })), } } @@ -146,16 +347,33 @@ export function validateLivingSystemPlan( ): LivingSystemPlanValidation { if (!plan || plan.eventId !== event.eventId) return { accepted: false, reason: 'event_mismatch' } if (plan.intent !== event.intent) return { accepted: false, reason: 'intent_mismatch' } - if (plan.route !== event.requestedRoute && plan.route !== 'world-login') { + if ( + plan.uiProjection.route !== plan.navigationAction.route + || ( + plan.uiProjection.route !== event.requestedRoute + && plan.uiProjection.route !== 'world-login' + ) + ) { return { accepted: false, reason: 'route_mismatch' } } const suppliedTruth = new Set(event.receiptIds) if (plan.requiredTruth.some(receiptId => !suppliedTruth.has(receiptId))) { return { accepted: false, reason: 'unverified_truth' } } - if (!event.worldOpen && PROTECTED_ROUTES.has(plan.route)) { + if (!event.worldOpen && PROTECTED_ROUTES.has(plan.uiProjection.route)) { return { accepted: false, reason: 'world_closed' } } + const expectedCapability = CAPABILITY_FOR_INTENT[event.intent] + if ( + !event.permissionBoundary.allowedCapabilities.includes(expectedCapability) + || ( + event.intent === 'navigate' + ? plan.capabilityCall !== null + : plan.capabilityCall?.capabilityId !== expectedCapability + ) + ) { + return { accepted: false, reason: 'capability_mismatch' } + } return { accepted: true, plan } } @@ -166,18 +384,39 @@ export function createDeterministicLivingSystemPlan( ? 'world-login' : event.requestedRoute const immersive = event.worldOpen && route !== 'world' && route !== 'world-login' + const capabilityId = CAPABILITY_FOR_INTENT[event.intent] return { version: GUANGHU_LIVING_SYSTEM_VERSION, eventId: event.eventId, intent: event.intent, planId: `fallback-${event.eventId}`, - route, requiredTruth: [], - scene: { - depth: immersive ? 'immersive' : route === 'world' ? 'overview' : 'focused', - motion: route === 'world' ? 'quiet' : 'responsive', - starDensity: immersive ? 'rich' : 'balanced', - connectionEmphasis: immersive ? 'active-route' : 'contextual', + uiProjection: { + route, + stateLabel: 'local-safe', + scene: { + depth: immersive ? 'immersive' : route === 'world' ? 'overview' : 'focused', + motion: route === 'world' ? 'quiet' : 'responsive', + starDensity: immersive ? 'rich' : 'balanced', + connectionEmphasis: immersive ? 'active-route' : 'contextual', + }, + }, + navigationAction: { type: 'navigate', route }, + capabilityCall: event.intent === 'navigate' + ? null + : { + type: 'capability', + capabilityId, + input: { + ...(event.appearanceTheme ? { appearanceTheme: event.appearanceTheme } : {}), + requestedRoute: route, + }, + }, + receiptSchema: { + outcome: 'pending-evidence', + requiredEvidence: event.intent === 'navigate' + ? ['ui.route.readback'] + : [`capability.${capabilityId}.result`], }, } } @@ -185,21 +424,32 @@ export function createDeterministicLivingSystemPlan( export function createLivingSystemExecutionReceipt({ plan, source, + outcome, + evidence, + error, now = Date.now(), }: { plan: GuanghuLivingSystemPlan source: GuanghuLivingSystemExecutionReceipt['source'] + outcome: GuanghuLivingSystemExecutionReceipt['outcome'] + evidence: string[] + error?: string now?: number }): GuanghuLivingSystemExecutionReceipt { + if (outcome === 'executed' && evidence.length === 0) { + throw new Error('guanghu_living_system_success_evidence_required') + } return { version: GUANGHU_LIVING_SYSTEM_VERSION, - receiptId: `local-execution:${plan.planId}`, + receiptId: `local-execution:${plan.planId}:${outcome}`, eventId: plan.eventId, intent: plan.intent, planId: plan.planId, - route: plan.route, + route: plan.uiProjection.route, source, - outcome: 'executed', - executedAt: now, + outcome, + evidence: [...evidence], + completedAt: now, + ...(error ? { error } : {}), } } diff --git a/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.test.ts b/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.test.ts index fe17ff943..0df65d784 100644 --- a/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.test.ts +++ b/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.test.ts @@ -30,26 +30,72 @@ const event = createLivingSystemEvent({ now: 1, }) +function typedPlan({ + eventId = 'event-001', + intent = 'navigate' as const, + requiredTruth = ['truth-001'], + stateLabel = 'model-proposed' as const, +} = {}) { + return { + version: 1 as const, + eventId, + intent, + planId: 'model-plan-001', + requiredTruth, + uiProjection: { + route: 'fifth-domain' as const, + stateLabel, + scene: { + depth: 'immersive' as const, + motion: 'active' as const, + starDensity: 'rich' as const, + connectionEmphasis: 'network' as const, + }, + }, + navigationAction: { type: 'navigate' as const, route: 'fifth-domain' as const }, + capabilityCall: null, + receiptSchema: { + outcome: 'pending-evidence' as const, + requiredEvidence: ['ui.route.readback'], + }, + } +} + describe('planGuanghuLivingSystem', () => { - it('uses the receipt-backed Shanghai lighthouse as the primary system model', async () => { + it('does not call a server planner without a verified current runtime binding', async () => { + const server = vi.fn().mockRejectedValue(new Error('legacy server route must stay closed')) + + await planGuanghuLivingSystem({ event, server }) + + expect(server).not.toHaveBeenCalled() + }) + + it('uses a server model only when the current runtime binding has a receipt', async () => { + const boundEvent = createLivingSystemEvent({ + eventId: 'event-001', + intent: 'navigate', + currentRoute: 'world', + requestedRoute: 'fifth-domain', + worldOpen: true, + receiptIds: ['truth-001', 'runtime-binding-receipt'], + runtimeBinding: { + bindingId: 'binding-current', + nodeId: 'JD-FD-PRIMARY', + personaSystemId: 'ICE-P-ZY001', + modelInstanceId: 'model-current', + issuedByReceiptId: 'runtime-binding-receipt', + status: 'verified', + }, + now: 1, + }) const server = vi.fn().mockResolvedValue({ source: 'server_lighthouse', serverReceiptId: 'GMRP-HOLOLAKE-001', model: 'deepseek-v4-flash', - plan: { - version: 1, - eventId: 'event-001', - intent: 'navigate', - planId: 'server-plan-001', - route: 'fifth-domain', - requiredTruth: ['truth-001'], - scene: { - depth: 'immersive', motion: 'responsive', starDensity: 'rich', connectionEmphasis: 'network', - }, - }, + plan: { ...typedPlan({ stateLabel: 'server-bound' }), planId: 'server-plan-001' }, }) - await expect(planGuanghuLivingSystem({ event, server })).resolves.toMatchObject({ + await expect(planGuanghuLivingSystem({ event: boundEvent, server })).resolves.toMatchObject({ source: 'server', serverReceiptId: 'GMRP-HOLOLAKE-001', plan: { planId: 'server-plan-001' }, @@ -59,37 +105,76 @@ describe('planGuanghuLivingSystem', () => { it('uses a valid model-native plan as the primary system path', async () => { const server = vi.fn().mockRejectedValue(new Error('offline')) const stream = vi.fn(async ({ callbacks }) => { - callbacks.onText(JSON.stringify({ - version: 1, - eventId: 'event-001', - intent: 'navigate', - planId: 'model-plan-001', - route: 'fifth-domain', - requiredTruth: ['truth-001'], - scene: { - depth: 'immersive', motion: 'active', starDensity: 'rich', connectionEmphasis: 'network', - }, - })) + callbacks.onText(JSON.stringify(typedPlan())) callbacks.onDone() }) await expect(planGuanghuLivingSystem({ event, target, stream, server })).resolves.toMatchObject({ source: 'model', - plan: { planId: 'model-plan-001', route: 'fifth-domain' }, + plan: { + planId: 'model-plan-001', + navigationAction: { route: 'fifth-domain' }, + }, }) }) it('returns an explicit fallback receipt when model output violates the boundary', async () => { const server = vi.fn().mockRejectedValue(new Error('offline')) const stream = vi.fn(async ({ callbacks }) => { - callbacks.onText('{"version":1,"eventId":"event-001","intent":"navigate","planId":"bad","route":"fifth-domain","requiredTruth":["invented"],"scene":{"depth":"immersive","motion":"active","starDensity":"rich","connectionEmphasis":"network"}}') + callbacks.onText(JSON.stringify({ + ...typedPlan({ requiredTruth: ['invented'] }), + planId: 'bad', + })) callbacks.onDone() }) await expect(planGuanghuLivingSystem({ event, target, stream, server })).resolves.toMatchObject({ source: 'fallback', fallbackReason: 'unverified_truth', - plan: { route: 'fifth-domain' }, + plan: { navigationAction: { route: 'fifth-domain' } }, + }) + }) + + it('fails closed for model callback errors, rejected streams, and timeouts', async () => { + const callbackErrorStream = vi.fn(async ({ callbacks }) => { + callbacks.onThinking('bounded') + callbacks.onToolStart('none') + callbacks.onToolDone('none') + callbacks.onError(new Error('model callback failed')) + callbacks.onDone() + }) + await expect(planGuanghuLivingSystem({ + event, + target, + stream: callbackErrorStream, + timeoutMs: 20, + })).resolves.toMatchObject({ + source: 'fallback', + fallbackReason: 'model_error', + }) + + const rejectedStream = vi.fn(async () => { + throw new Error('stream rejected') + }) + await expect(planGuanghuLivingSystem({ + event, + target, + stream: rejectedStream, + timeoutMs: 20, + })).resolves.toMatchObject({ + source: 'fallback', + fallbackReason: 'model_error', + }) + + const timedOutStream = vi.fn(async () => new Promise(() => {})) + await expect(planGuanghuLivingSystem({ + event, + target, + stream: timedOutStream, + timeoutMs: 1, + })).resolves.toMatchObject({ + source: 'fallback', + fallbackReason: 'timeout', }) }) }) diff --git a/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.ts b/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.ts index 776a6fd0d..b0a8b796a 100644 --- a/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.ts +++ b/product-source/hololake-platform/src/utils/planGuanghuLivingSystem.ts @@ -18,14 +18,15 @@ const DEFAULT_TIMEOUT_MS = 1_400 const LIVING_SYSTEM_PROMPT = [ 'You are the non-conversational HoloLake living-system planner.', - 'Transform exactly one verified interaction event into one version-1 system plan.', + 'Transform exactly one bounded interaction event into one version-1 typed system plan.', 'Return one compact JSON object only. Never return prose, markdown, explanations, or tool calls.', - 'Keep eventId and intent unchanged. route must equal requestedRoute, except world-login may be used as a safe gate.', + 'Keep eventId and intent unchanged.', + 'uiProjection.route and navigationAction.route must equal requestedRoute, except world-login may be used as a safe gate.', 'requiredTruth may contain only receiptIds supplied by the event.', - 'scene.depth: overview|focused|immersive.', - 'scene.motion: quiet|responsive|active.', - 'scene.starDensity: sparse|balanced|rich.', - 'scene.connectionEmphasis: contextual|active-route|network.', + 'uiProjection.scene is a visual projection of supplied state, not a theme score.', + 'navigationAction is typed and cannot substitute another host action.', + 'capabilityCall must use the exact registered capability for the event intent, or null for navigate.', + 'receiptSchema must remain pending-evidence; never claim execution success.', 'Do not infer authorization, server state, identity, or execution success.', ].join(' ') @@ -39,6 +40,14 @@ type LivingSystemStreamRequest = { type LivingSystemStream = (request: LivingSystemStreamRequest) => Promise +const CAPABILITY_FOR_INTENT: Record = { + navigate: null, + 'open-knowledge': 'knowledge.open', + 'open-agent-workspace': 'agent.workspace.open', + 'open-local-workspace': 'local.workspace.open', + 'apply-theme': 'appearance.apply', +} + export type LivingSystemPlanResult = { source: 'server' | 'model' | 'fallback' plan: GuanghuLivingSystemPlan @@ -60,28 +69,34 @@ type PlanGuanghuLivingSystemOptions = { function eventPrompt(event: GuanghuLivingSystemEvent): string { return JSON.stringify({ version: 1, - event: { - eventId: event.eventId, - intent: event.intent, - currentRoute: event.currentRoute, - requestedRoute: event.requestedRoute, - appearanceTheme: event.appearanceTheme, - worldOpen: event.worldOpen, - receiptIds: event.receiptIds, - occurredAt: event.occurredAt, - }, + event, output: { version: 1, eventId: event.eventId, intent: event.intent, planId: 'unique string', - route: event.requestedRoute, requiredTruth: [], - scene: { - depth: 'overview|focused|immersive', - motion: 'quiet|responsive|active', - starDensity: 'sparse|balanced|rich', - connectionEmphasis: 'contextual|active-route|network', + uiProjection: { + route: event.requestedRoute, + stateLabel: event.currentSystemState.runtimeBinding ? 'server-bound' : 'model-proposed', + scene: { + depth: 'overview|focused|immersive', + motion: 'quiet|responsive|active', + starDensity: 'sparse|balanced|rich', + connectionEmphasis: 'contextual|active-route|network', + }, + }, + navigationAction: { type: 'navigate', route: event.requestedRoute }, + capabilityCall: event.intent === 'navigate' + ? null + : { + type: 'capability', + capabilityId: CAPABILITY_FOR_INTENT[event.intent], + input: {}, + }, + receiptSchema: { + outcome: 'pending-evidence', + requiredEvidence: [], }, }, }) @@ -147,19 +162,21 @@ export async function planGuanghuLivingSystem({ stream = streamAiModel, timeoutMs = DEFAULT_TIMEOUT_MS, }: PlanGuanghuLivingSystemOptions): Promise { - try { - const serverResult = await server(event) - const validation = validateLivingSystemPlan(event, serverResult.plan) - if (validation.accepted) { - return { - source: 'server', - plan: validation.plan, - serverReceiptId: serverResult.serverReceiptId, + if (event.currentSystemState.runtimeBinding?.status === 'verified') { + try { + const serverResult = await server(event) + const validation = validateLivingSystemPlan(event, serverResult.plan) + if (validation.accepted) { + return { + source: 'server', + plan: validation.plan, + serverReceiptId: serverResult.serverReceiptId, + } } + } catch { + // A receipt-bound runtime is optional. A configured local model or the + // deterministic recovery plan keeps local navigation available. } - } catch { - // The server route is the primary runtime. A configured direct model or - // the deterministic recovery plan keeps navigation available if it is absent. } if (!target) return fallback(event, 'model_error') const model = await collectModelOutput(stream, target, event, timeoutMs) diff --git a/product-source/hololake-platform/standards/GLS-0844.md b/product-source/hololake-platform/standards/GLS-0844.md index e68b16f15..ffdb010e0 100644 --- a/product-source/hololake-platform/standards/GLS-0844.md +++ b/product-source/hololake-platform/standards/GLS-0844.md @@ -41,9 +41,32 @@ GHNQG 是光湖代码频道内生的源码验收协议。它不向任何外部 ## 执行归属 -施工期由世界包内的 `scripts/run-guanghu-native-quality-gate.sh` 在宿主工具链 -上执行。终态执行体为 `GOSK_CODE_CHANNEL_QUALITY_EXECUTOR`。迁移改变执行 -位置,不改变二值规则、必需门或回执语义。 +HoloLake 施工期由产品仓库内的 +`scripts/run-hololake-native-quality-gate.sh` 在宿主工具链上自动执行;世界包 +执行器只验证它自身,不能替 HoloLake 签发产品回执。终态执行体为 +`GOSK_CODE_CHANNEL_QUALITY_EXECUTOR`。迁移改变执行位置,不改变二值规则、 +必需门或回执语义。 外部分析器可以被人类主动调用作为观察意见,但其不存在、无账号、无额度 或给出不同评分,都不能授权或阻止光湖代码频道。 + +## HoloLake 产品层绑定 + +HoloLake 的开发、测试、打包、发布和第五域代码频道都继承同一套原生权威, +不得另建产品层评分宪法。执行时遵循 GLS-0101 的 +`Parse → Validate → Resolve → Authorize → Execute → Verify → Write Back` +骨架,并同时绑定: + +- `GLS-0247`:从注册表解析当前有效协议与替代关系; +- `TCS / GLS-0200`:保存需求、判断边界与认知因果; +- `HLDP / GLS-0400`:追加式保存变更、纠正、提交树和回执; +- `GLP`:传递任务、权限、结果与失败,不把消息当成执行事实; +- `GHNQG / GLS-0844`:只签发 `PASS_100` 或 `FAIL_0`。 + +测试框架、编译器、代码托管、流水线和侧车都是施工工具。它们可以执行或 +观察某个必需门,但不能定义第三种状态,也不能取代注册表、精确源码树和原生 +回执。 + +仓库内 `scripts/test-guanghu-native-authority.sh` 负责阻止旧外部评分规则重新 +进入当前生效面;HoloLake 产品执行器负责汇总前端、原生端、世界与协议证据, +并签发绑定精确提交与源码树的最终回执。 diff --git a/product-source/hololake-platform/standards/guanghu-native-engineering-profile.json b/product-source/hololake-platform/standards/guanghu-native-engineering-profile.json new file mode 100644 index 000000000..79ad8dde7 --- /dev/null +++ b/product-source/hololake-platform/standards/guanghu-native-engineering-profile.json @@ -0,0 +1,157 @@ +{ + "schema": "guanghu.native-engineering-profile/v1", + "profileId": "HLP-NATIVE-ENGINEERING-001", + "product": "HoloLake", + "authority": { + "repository": "REPO-012", + "remote": "https://guanghulab.com/code/bingshuo/guanghu-ice-heart", + "commit": "488a4792bbe4f3678d67a247540b14d28d5d3a62", + "registry": "gls/GLS-PROTOCOL-REGISTRY.json", + "registryId": "GLS-PROTOCOL-REGISTRY-20260731", + "scope": "FIFTH_DOMAIN_CODE_CHANNEL_ONLY" + }, + "decisionModel": { + "allowedReceiptStates": ["FAIL_0", "PASS_100"], + "aggregateRule": "ALL_REQUIRED_GATES_100_OR_TOTAL_0", + "registrationIsImplementation": false, + "externalScoringHasAuthority": false + }, + "automaticTriggers": [ + { + "event": "development_start", + "surface": "package.predev", + "gate": "native_authority_and_protocol_binding" + }, + { + "event": "test_start", + "surface": "package.pretest", + "gate": "native_authority_and_protocol_binding" + }, + { + "event": "build_start", + "surface": "package.prebuild", + "gate": "native_authority_protocol_binding_and_core_coverage_100" + }, + { + "event": "source_commit", + "surface": ".husky/pre-commit", + "gate": "native_authority_and_protocol_binding" + }, + { + "event": "source_publish", + "surface": ".husky/pre-push", + "gate": "GHNQG" + }, + { + "event": "code_channel_review", + "surface": ".github/workflows/ci.yml", + "gate": "GHNQG" + }, + { + "event": "living_system_event", + "surface": "src/lib/guanghuLivingSystem.ts#createLivingSystemEvent", + "gate": "identity_state_permission_responsibility_capability" + }, + { + "event": "model_plan_returned", + "surface": "src/lib/guanghuLivingSystem.ts#validateLivingSystemPlan", + "gate": "typed_output_and_verified_truth" + }, + { + "event": "server_model_request", + "surface": "src/utils/planGuanghuLivingSystem.ts#planGuanghuLivingSystem", + "gate": "receipt_backed_runtime_binding" + }, + { + "event": "execution_receipt_requested", + "surface": "src/lib/guanghuLivingSystem.ts#createLivingSystemExecutionReceipt", + "gate": "real_evidence_before_success" + } + ], + "protocolBindings": [ + { + "protocol": "GLS-0110", + "acronym": "ISRP", + "engineeringRole": "Intent is parsed into one registered action and cannot be substituted by a model.", + "implementation": ["src/lib/guanghuLivingSystem.ts"] + }, + { + "protocol": "GLS-0200", + "acronym": "TCS", + "engineeringRole": "Persona identity, relationship, responsibility and cognition remain explicit model inputs.", + "implementation": ["src/lib/guanghuLivingSystem.ts", "src/utils/planGuanghuLivingSystem.ts"] + }, + { + "protocol": "GLS-0230", + "acronym": "GLOW-SOURCE-GUARD", + "engineeringRole": "External source and tools are observations or isolated material, never product authority or body.", + "implementation": ["scripts/test-guanghu-native-authority.sh"] + }, + { + "protocol": "GLS-0306", + "acronym": "GLP-RECEIPT", + "engineeringRole": "Execution success is emitted only after deterministic host evidence exists.", + "implementation": ["src/lib/guanghuLivingSystem.ts", "src/components/HoloLakeHome.tsx"] + }, + { + "protocol": "GLS-0311", + "acronym": "GLOW", + "engineeringRole": "Visible execution preserves source, outcome, evidence and failure instead of replacing them with a score.", + "implementation": ["src/lib/guanghuLivingSystem.ts", "src/components/HoloLakeHome.tsx"] + }, + { + "protocol": "GLS-0400", + "acronym": "HLDP", + "engineeringRole": "Receipts and corrections are append-only evidence addresses; summaries do not overwrite source truth.", + "implementation": ["src/lib/guanghuLivingSystem.ts"] + }, + { + "protocol": "GLS-0708", + "acronym": "GMRP", + "engineeringRole": "A server model is callable only through a receipt-backed current runtime binding.", + "implementation": ["src/utils/planGuanghuLivingSystem.ts", "src-tauri/src/guanghu_living_system.rs"] + }, + { + "protocol": "GLS-0710", + "acronym": "GMP", + "engineeringRole": "Every executable capability has a stable identity, typed input and receipt output.", + "implementation": ["src/lib/guanghuLivingSystem.ts"] + }, + { + "protocol": "GLS-0803", + "acronym": "PALP", + "engineeringRole": "Persona is the responsible brain; bounded capabilities are hands and never acquire subject authority.", + "implementation": ["src/lib/guanghuLivingSystem.ts"] + }, + { + "protocol": "GLS-0810", + "acronym": "LPOS", + "engineeringRole": "HoloLake projects the language-persona operating system instead of attaching a chat or theme adapter.", + "implementation": ["src/components/HoloLakeHome.tsx"] + }, + { + "protocol": "GLS-0842", + "acronym": "HLSP", + "engineeringRole": "Online world state requires a live-session receipt; a UI label or repository record is not a session.", + "implementation": ["src/lib/guanghuLivingSystem.ts", "src/components/HoloLakeHome.tsx"] + }, + { + "protocol": "GLS-0844", + "acronym": "GHNQG", + "engineeringRole": "All applicable native gates must pass for one exact source tree; no partial score grants authority.", + "implementation": ["scripts/run-hololake-native-quality-gate.sh", ".husky/pre-push", ".github/workflows/ci.yml"] + } + ], + "requiredInvariants": [ + "persona_system_context_is_explicit", + "current_system_knowledge_permission_responsibility_and_capability_inputs_are_explicit", + "model_outputs_are_typed_projection_navigation_capability_and_receipt_schema", + "runtime_binding_is_receipt_backed", + "legacy_server_route_is_not_auto_resurrected", + "success_requires_execution_evidence", + "auditable_native_core_lines_and_functions_are_100", + "third_party_scores_have_no_authority", + "registered_protocol_source_is_traceable", + "hololake_quality_receipt_is_issued_by_product_native_gate" + ] +} diff --git a/product-source/hololake-platform/tests/smoke/contribute-modal.spec.ts b/product-source/hololake-platform/tests/smoke/contribute-modal.spec.ts index 09b78ade2..304ecd1bc 100644 --- a/product-source/hololake-platform/tests/smoke/contribute-modal.spec.ts +++ b/product-source/hololake-platform/tests/smoke/contribute-modal.spec.ts @@ -44,16 +44,6 @@ test.describe('Contribute modal', () => { await page.keyboard.press('Enter') await expect.poll(async () => page.evaluate(() => (window as typeof window & { __tolariaOpenedUrls: string[] }).__tolariaOpenedUrls)).toContain('https://refactoring.fm/') - await page.keyboard.press('Tab') - await expect(page.getByRole('button', { name: 'Open Codacy' })).toBeFocused() - await page.keyboard.press('Enter') - await expect.poll(async () => page.evaluate(() => (window as typeof window & { __tolariaOpenedUrls: string[] }).__tolariaOpenedUrls)).toContain('https://www.codacy.com/') - - await page.keyboard.press('Tab') - await expect(page.getByRole('button', { name: 'Open CodeScene' })).toBeFocused() - await page.keyboard.press('Space') - await expect.poll(async () => page.evaluate(() => (window as typeof window & { __tolariaOpenedUrls: string[] }).__tolariaOpenedUrls)).toContain('https://codescene.com/') - await page.keyboard.press('Tab') await expect(page.getByRole('button', { name: 'Open CircleCI' })).toBeFocused() await page.keyboard.press('Enter') diff --git a/product-source/hololake-platform/vite.config.ts b/product-source/hololake-platform/vite.config.ts index fdb55a8b8..950a30e19 100644 --- a/product-source/hololake-platform/vite.config.ts +++ b/product-source/hololake-platform/vite.config.ts @@ -1038,12 +1038,6 @@ export default defineConfig({ 'src/components/ui/tooltip.tsx', 'src/components/ui/card.tsx', ], - thresholds: { - lines: 70, - functions: 70, - branches: 70, - statements: 70, - }, }, }, }) diff --git a/product-source/hololake-platform/vitest.guanghu-native.config.ts b/product-source/hololake-platform/vitest.guanghu-native.config.ts new file mode 100644 index 000000000..8dfefafd0 --- /dev/null +++ b/product-source/hololake-platform/vitest.guanghu-native.config.ts @@ -0,0 +1,35 @@ +import os from 'node:os' +import path from 'node:path' +import { existsSync } from 'node:fs' +import { defineConfig } from 'vitest/config' + +const reportsDirectory = process.env.GHNQG_COVERAGE_DIR + ?? ( + existsSync('/Volumes/JZAO/HoloLake/artifacts') + ? '/Volumes/JZAO/HoloLake/artifacts/hololake-native-quality/coverage/current' + : path.join(os.tmpdir(), 'hololake-ghnqg-coverage') + ) + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + include: [ + 'src/lib/guanghuLivingSystem.test.ts', + 'src/utils/planGuanghuLivingSystem.test.ts', + ], + coverage: { + provider: 'v8', + reporter: ['text', 'json-summary'], + reportsDirectory, + include: [ + 'src/lib/guanghuLivingSystem.ts', + 'src/utils/planGuanghuLivingSystem.ts', + ], + thresholds: { + lines: 100, + functions: 100, + }, + }, + }, +})