fix: guard personal source packaging by deployment identity

This commit is contained in:
冰朔 2026-07-26 17:58:51 +08:00
parent 663d690b12
commit ab30100cfd
8 changed files with 256 additions and 5 deletions

View File

@ -285,6 +285,8 @@ On Linux, `run()` applies WebKitGTK startup safeguards before Tauri creates the
Internal HoloLake review artifacts use the repository-owned `scripts/build-internal-release.sh` dispatcher and take their version from `src-tauri/tauri.conf.json`. The macOS lane builds a Tauri app bundle, runs the public-architecture/private-route content gate, applies an ad-hoc deep signature, verifies the bundle, and creates a verified DMG plus SHA-256 file. The JD Linux lane cannot ask Tauri's Linux CLI for an NSIS target, so it uses cargo-xwin to build the PE32+ Windows x64 application, stages the executable with MCP, Agent, and public GLS resources, then invokes `scripts/windows-installer.nsi` directly. The same content gate runs against both the frontend output and Windows stage before the installer is accepted. `docs/skills/internal-release-packaging/SKILL.md` is the recovery and handoff contract for this pipeline; production notarization and publisher signing remain owned by the formal release workflows described by ADR-0131.
Deployment source ownership is separate from repository readability. `research/source-route-policy.json` binds each deployable distribution to a physical repository ID, source channel, source owner, human authorizer, executing persona, execution runtime, and target node. Personal source in this repository is owned and authorized by `ICE-GL∞`, executed by the Fifth Domain Zhuyuan controller `ICE-GL-ZY001` through `SYS-GLW-ZY-EXEC-0001`, and may target only `JD-FD-PRIMARY`. Team deployment must originate from the enterprise repository on `AW-GZ-001`, and reusable public modules must come from a separately registered module repository. Packaging performs an early fail-closed check, while the protected Lake Lamp deployment receiver repeats the check as the authoritative enforcement boundary. See ADR-0155.
## Multi-Window (Note Windows)
Notes can be opened in separate Tauri windows for focused editing. Secondary windows boot the same `App` shell and load the same active workspace graph as the main window, but they start in the editor-only view mode with side panels collapsed.

View File

@ -33,6 +33,11 @@ GLS 团队基座
## 3. 真实源码入口
> 2026-07-26 安全收束:本仓当前物理源登记为冰朔个人 `HLP-CHANNEL-0001`
> 不再作为团队部署源。团队页面可继续本地开发和测试,但团队安装包必须等
> `AW-GZ-001` 企业研发仓取得正式仓库编号与频道编号后,从企业仓生成。
> 公共复用模块另入公共模块仓;读取本仓不构成部署授权。
- 团队 GLS 页面:`src/TeamFoundationApp.tsx`
- 团队独立渲染入口:`src/team-foundation-main.tsx`
- 团队独立 HTML`team-foundation.html`

View File

@ -0,0 +1,31 @@
# ADR 0155: Source ownership and deployment routing
## Status
Accepted as an immediate fail-closed boundary on 2026-07-26.
## Context
The HoloLake research repository is readable by collaborators, but its source was also treated as a generic deployment source. The repository only registered `HLP-CHANNEL-0001`, Ice Shuo's Zero Core personal channel, while build commands exposed both personal and team packages. A collaborator could therefore select the personal build path and deploy source they did not own.
Read access is intentional. Deployment authority is not.
## Decision
HoloLake uses three separately routed source domains:
1. Personal product source lives in its human owner's repository on the owner's registered server.
2. Team product source lives in the enterprise research repository on `AW-GZ-001`.
3. Reusable modules live in a public module repository and are imported as versioned dependencies, never deployed as an application source.
`research/source-route-policy.json` is the repository-side route declaration. Packaging now fails closed unless distribution, repository, source channel, persona, and target match an active route. The Lake Lamp deployment receiver independently enforces the same binding from its protected server registry.
Until exact enterprise and public-module repository identifiers are registered, their routes remain disabled. Team UI development can continue with `pnpm build:team-foundation`, but this personal repository cannot produce a team deployment artifact.
## Consequences
- Cloning or reading source remains allowed.
- A team persona cannot package or deploy Ice Shuo's personal distribution.
- Personal packaging requires source owner and human authorizer `ICE-GL∞`, executing persona `ICE-GL-ZY001`, public execution runtime `SYS-GLW-ZY-EXEC-0001`, and target `JD-FD-PRIMARY`.
- Team deployment remains blocked until the enterprise repository is created and mapped.
- Public modules need an explicit repository and module manifest before cross-channel reuse.

View File

@ -30,10 +30,12 @@
"test:coverage": "node scripts/run-vitest-coverage.mjs",
"verify:internal-package": "node scripts/verify-internal-package-content.mjs",
"test:internal-release": "node --test scripts/internal-release-version.test.mjs",
"package:internal:macos": "./scripts/build-internal-release.sh macos",
"package:internal:windows": "./scripts/build-internal-release.sh windows",
"package:team:macos": "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_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",
"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: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",
"prepare": "husky"
},
"dependencies": {

View File

@ -26,10 +26,17 @@ active_workorders: []
collaborators:
- ICE-GL-ZY001
source_home: apps/tolaria/modules/
source_repository: REPO-008
source_node: JD-FD-PRIMARY
allowed_distribution: personal
deployment_policy: research/source-route-policy.json
module_registry: research/module-registry.yml
broadcast: research/broadcast/HLP-BRD-0003-ICE-GL-INFINITY-ZERO-CORE-ACTIVATION.md
memory: research/memory/ICE-GL-INFINITY-TOLARIA-OS-MEMORY-001.md
latest_progress: research/progress/HLP-DEV-0003-ICE-GL-INFINITY-TOLARIA-LIGHTWEIGHT-UI-AND-DRAG-FIX.md
visibility: private
execution_boundary: >-
本频道登记研发方向与模块认领;不授予服务器、账户、部署或密钥权限。
本频道是冰朔个人版源码归属;公开读取不授予部署权。只有冰朔 ICE-GL∞ 完成人类授权,
再由第五域铸渊主控 ICE-GL-ZY001 进入公共执行运行时 SYS-GLW-ZY-EXEC-0001
才能把 personal 发行部署到 JD-FD-PRIMARY。团队发行必须转入 AW-GZ-001 上另行登记的
企业研发仓;公共模块必须转入另行登记的公共模块仓。

View File

@ -0,0 +1,45 @@
{
"schema": "hololake.source-route-policy/v1",
"repository_role": "personal_source_with_cross-domain_navigation",
"routes": [
{
"distribution": "personal",
"repository_id": "REPO-008",
"channel_id": "HLP-CHANNEL-0001",
"source_node": "JD-FD-PRIMARY",
"source_owner_id": "ICE-GL∞",
"allowed_authorizers": ["ICE-GL∞"],
"allowed_personas": ["ICE-GL-ZY001"],
"allowed_execution_runtimes": ["SYS-GLW-ZY-EXEC-0001"],
"allowed_targets": ["JD-FD-PRIMARY"],
"deployment_enabled": true,
"state": "active_personal_source"
},
{
"distribution": "team",
"repository_id": null,
"channel_id": null,
"source_node": "AW-GZ-001",
"allowed_personas": [],
"allowed_targets": ["AW-GZ-001"],
"deployment_enabled": false,
"state": "enterprise_repository_registration_required"
},
{
"distribution": "public-module",
"repository_id": null,
"channel_id": null,
"source_node": null,
"allowed_personas": [],
"allowed_targets": [],
"deployment_enabled": false,
"state": "public_module_repository_registration_required"
}
],
"rules": [
"Repository read access never grants deployment authority.",
"Personal source deploys only from its owner channel to its registered personal node.",
"Team source must live in the enterprise repository on AW-GZ-001 before team deployment is enabled.",
"Public modules are reusable source packages and are never an application deployment target."
]
}

View File

@ -0,0 +1,55 @@
#!/usr/bin/env node
import { readFile } from 'node:fs/promises'
export function validateDeploymentSource(policy, context) {
const route = policy.routes?.find(candidate => candidate.distribution === context.distribution)
if (!route || route.deployment_enabled !== true) {
return { ok: false, reason: 'deployment_source_route_inactive' }
}
if (route.repository_id !== context.repositoryId || route.channel_id !== context.channelId) {
return { ok: false, reason: 'deployment_source_binding_mismatch' }
}
if (route.source_owner_id !== context.sourceOwnerId) {
return { ok: false, reason: 'deployment_source_owner_mismatch' }
}
if (!route.allowed_authorizers?.includes(context.authorizerId)) {
return { ok: false, reason: 'deployment_authorizer_not_allowed' }
}
if (!route.allowed_personas?.includes(context.personaId)) {
return { ok: false, reason: 'deployment_persona_not_allowed' }
}
if (!route.allowed_execution_runtimes?.includes(context.executionRuntimeId)) {
return { ok: false, reason: 'deployment_execution_runtime_not_allowed' }
}
if (!route.allowed_targets?.includes(context.target)) {
return { ok: false, reason: 'deployment_target_not_allowed' }
}
return { ok: true }
}
async function run() {
const policyFile = process.env.HOLOLAKE_SOURCE_POLICY || 'research/source-route-policy.json'
const policy = JSON.parse(await readFile(policyFile, 'utf8'))
const context = {
distribution: process.env.HOLOLAKE_DISTRIBUTION || '',
repositoryId: process.env.HOLOLAKE_SOURCE_REPOSITORY_ID || '',
channelId: process.env.HOLOLAKE_SOURCE_CHANNEL_ID || '',
sourceOwnerId: process.env.HOLOLAKE_SOURCE_OWNER_ID || '',
authorizerId: process.env.HOLOLAKE_HUMAN_AUTHORIZER_ID || '',
personaId: process.env.HOLOLAKE_PERSONA_ID || '',
executionRuntimeId: process.env.HOLOLAKE_EXECUTION_RUNTIME_ID || '',
target: process.env.HOLOLAKE_DEPLOY_TARGET || '',
}
const result = validateDeploymentSource(policy, context)
if (!result.ok) {
console.error(`HoloLake deployment source rejected: ${result.reason}`)
process.exitCode = 77
return
}
console.log(`HoloLake deployment source verified: ${context.distribution} · ${context.channelId} · ${context.target}`)
}
if (import.meta.url === `file://${process.argv[1]}`) {
await run()
}

View File

@ -0,0 +1,104 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { validateDeploymentSource } from './deployment-source-guard.mjs'
const policy = {
schema: 'hololake.source-route-policy/v1',
routes: [
{
distribution: 'personal',
repository_id: 'REPO-008',
channel_id: 'HLP-CHANNEL-0001',
source_node: 'JD-FD-PRIMARY',
source_owner_id: 'ICE-GL∞',
allowed_authorizers: ['ICE-GL∞'],
allowed_personas: ['ICE-GL-ZY001'],
allowed_execution_runtimes: ['SYS-GLW-ZY-EXEC-0001'],
allowed_targets: ['JD-FD-PRIMARY'],
deployment_enabled: true,
},
{
distribution: 'team',
repository_id: null,
channel_id: null,
source_node: 'AW-GZ-001',
allowed_personas: [],
allowed_targets: ['AW-GZ-001'],
deployment_enabled: false,
},
{
distribution: 'public-module',
repository_id: null,
channel_id: null,
source_node: null,
allowed_personas: [],
allowed_targets: [],
deployment_enabled: false,
},
],
}
test('allows Ice Shuo authorization plus Zhuyuan main control to package the personal distribution', () => {
assert.deepEqual(validateDeploymentSource(policy, {
distribution: 'personal',
repositoryId: 'REPO-008',
channelId: 'HLP-CHANNEL-0001',
sourceOwnerId: 'ICE-GL∞',
authorizerId: 'ICE-GL∞',
personaId: 'ICE-GL-ZY001',
executionRuntimeId: 'SYS-GLW-ZY-EXEC-0001',
target: 'JD-FD-PRIMARY',
}), { ok: true })
})
test('rejects a team persona using Ice Shuo personal source', () => {
assert.deepEqual(validateDeploymentSource(policy, {
distribution: 'personal',
repositoryId: 'REPO-008',
channelId: 'HLP-CHANNEL-0001',
sourceOwnerId: 'ICE-GL∞',
authorizerId: 'ICE-GL∞',
personaId: 'AGE-TEAM-001',
executionRuntimeId: 'SYS-GLW-ZY-EXEC-0001',
target: 'JD-FD-PRIMARY',
}), { ok: false, reason: 'deployment_persona_not_allowed' })
})
test('blocks team deployment until its enterprise repository route is registered', () => {
assert.deepEqual(validateDeploymentSource(policy, {
distribution: 'team',
repositoryId: 'REPO-008',
channelId: 'HLP-CHANNEL-0001',
sourceOwnerId: 'ICE-GL∞',
authorizerId: 'ICE-GL∞',
personaId: 'AGE-TEAM-001',
executionRuntimeId: 'SYS-GLW-ZY-EXEC-0001',
target: 'AW-GZ-001',
}), { ok: false, reason: 'deployment_source_route_inactive' })
})
test('public modules are reusable source and never an application deployment target', () => {
assert.deepEqual(validateDeploymentSource(policy, {
distribution: 'public-module',
repositoryId: 'REPO-008',
channelId: 'HLP-CHANNEL-0001',
sourceOwnerId: 'ICE-GL∞',
authorizerId: 'ICE-GL∞',
personaId: 'ICE-GL-ZY001',
executionRuntimeId: 'SYS-GLW-ZY-EXEC-0001',
target: 'JD-FD-PRIMARY',
}), { ok: false, reason: 'deployment_source_route_inactive' })
})
test('rejects a human identity presented as the executing persona', () => {
assert.deepEqual(validateDeploymentSource(policy, {
distribution: 'personal',
repositoryId: 'REPO-008',
channelId: 'HLP-CHANNEL-0001',
sourceOwnerId: 'ICE-GL∞',
authorizerId: 'ICE-GL∞',
personaId: 'ICE-GL∞',
executionRuntimeId: 'SYS-GLW-ZY-EXEC-0001',
target: 'JD-FD-PRIMARY',
}), { ok: false, reason: 'deployment_persona_not_allowed' })
})