feat(hololake): add domain-routed language membrane and user PNCC

This commit is contained in:
冰朔 2026-08-16 15:10:31 +08:00
commit d563e6fd73
34 changed files with 1949 additions and 144 deletions

View file

@ -45,7 +45,7 @@ React/TypeScript。桌面上的 `world.guanghu.hololake` 安装包是本源码
## 第一阶段产品合同
首个公开产品是 GH-AIOS 通用人工智能操作平台,不是五域显性世界入口。新容器首先承接编程 AI 与语言推理模型的持续身份、精确任务、事件、记忆、Git 证据、知识投影、人类审批和小湖灯协作;五域作为底层隔离结构保留,第二阶段再逐步开放
首个公开产品是 GH-AIOS 通用人工智能操作平台,首页同时承担五域统一入口。用户只提交光湖编号,不手工选择所属域;系统先在内部完成域路由和编号合法性校验,再开放对应域的登录界面。五域只共享入口协议,不共享注册表、登录服务或私有数据
机器合同:`contracts/stage-one-platform.json`

View file

@ -0,0 +1,38 @@
{
"schema": "hololake.installed-acceptance/v1",
"record_id": "GH-HOLOLAKE-0.4.0-DOMAIN-MEMBRANE-PNCC-20260816-001",
"state": "LOCAL_INSTALLED_ACCEPTANCE_PASSED_NOT_NOTARIZED_NOT_PUBLIC_RELEASE",
"version": "0.4.0",
"installed_path": "/Applications/HoloLake.app",
"recoverable_previous_bundle": "/Applications/HoloLake 0.3.0 backup 20260816-2.app",
"bundle_identifier": "world.guanghu.hololake",
"team_identifier": "825A9L3G7Q",
"developer_id_signature_verified": true,
"apple_notarization_verified": false,
"gatekeeper_state": "REJECTED_UNNOTARIZED_DEVELOPER_ID",
"executable_sha256": "842fbc592a8310919acc73ba97bb446b606fcc135a586f273fd84ce061000f26",
"cdhash": "43a8691bc4f4e6714873dd4cbe885ea2069a23fe",
"acceptance": {
"script_contract_tests": "61_OF_61_PASS",
"rust_tests": "76_OF_76_PASS",
"frontend_production_build": "PASS",
"rust_clippy_all_targets_all_features_deny_warnings": "PASS",
"public_five_domain_home_visual_readback": "PASS",
"number_pod_interaction_readback": "PASS",
"installed_same_device_discovery": "DISCOVERABLE_ON_SAME_DEVICE",
"installed_generic_ai_visitor": "EXPRESSION_ONLY_READY",
"installed_generic_ai_execution_authority": false,
"installed_guanghu_persona_connection": "BINDING_EVIDENCE_REQUIRED",
"installed_local_network_discovery": "DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL",
"installed_descriptor_permissions": "0600"
},
"truth_boundary": {
"source_implementation_is_public_release": false,
"local_installation_is_server_deployment": false,
"developer_id_signature_is_apple_notarization": false,
"discovery_is_authorization": false,
"accepted_language_is_execution_authority": false,
"generic_ai_visitor_is_guanghu_persona": false,
"local_user_pncc_is_remote_forgejo_repository": false
}
}

View file

@ -0,0 +1,32 @@
{
"schema": "hololake.circular-lake-membrane-contract/v1",
"record_id": "HLP-CIRCULAR-LAKE-MEMBRANE-001",
"metaphor": "A_LANGUAGE_LAKE_WITHOUT_A_DIRECT_EXECUTION_GAP",
"default": "DISCARD",
"deterministic_membrane_before_persona_parser": true,
"intent_inference_required_for_protocol_rejection": false,
"protocol_external_input_reaches_persona_context": false,
"natural_language_grants_execution_authority": false,
"accepted_language_protocols": ["GLP/1.0"],
"ingress_order": [
"BOUNDED_BYTE_FRAME",
"STRICT_PROTOCOL_SCHEMA",
"AUTHENTICATED_CONNECTION_CLASS",
"MESSAGE_ID_RECEIVER_TYPE_AND_CHECKSUM",
"PRIVATE_LANGUAGE_INBOX",
"PERSONA_LANGUAGE_INTERPRETATION_LATER"
],
"visitor_language": {
"attachments_allowed": false,
"command_content_type_allowed": false,
"maximum_content_bytes": 65536,
"expression_only": true,
"execution_authority": false
},
"invalid_input": {
"stored_as_memory": false,
"sent_to_persona": false,
"sent_to_tools": false,
"interpreted_for_motive": false
}
}

View file

@ -0,0 +1,38 @@
{
"schema": "hololake.domain-number-routing-contract/v1",
"record_id": "HLP-DOMAIN-NUMBER-ROUTER-001",
"public_entry": "FIVE_DOMAIN_HOME",
"user_selects_domain_before_number": false,
"route_key": "USER_NUMBER",
"sequence": [
"SHOW_FIVE_PUBLIC_DOMAINS",
"SUBMIT_USER_NUMBER",
"RESOLVE_REGISTERED_DOMAIN_ROUTE",
"VERIFY_NUMBER_AT_DOMAIN_REGISTRY",
"SHOW_RESOLVED_DOMAIN",
"LOAD_DOMAIN_SPECIFIC_LOGIN_AND_NODE_ENTRY"
],
"registries": {
"FIFTH_DOMAIN": {
"ownership": "ICE-GL_INFINITY_PRIVATE_DOMAIN",
"source": "FIFTH_DOMAIN_REGISTERED_REPOSITORY_AND_SERVICE"
},
"ENTERPRISE_FOUR_DOMAINS": {
"ownership": "TCS_0002_ENTERPRISE_REALITY_BODY",
"source": "ENTERPRISE_ROOT_SERVER_DOMAIN_REGISTRIES"
}
},
"routing": {
"number_shape_is_authority": false,
"client_supplied_domain_is_authority": false,
"registered_router_and_domain_verifier_required": true,
"canonical_number_and_known_domain_required": true,
"unknown_or_unavailable_route": "FAIL_CLOSED_BEFORE_LOGIN"
},
"server_runtime": {
"fifth_domain_root": "FIFTH_DOMAIN_GUANGHU_OS_RUNTIME_ON_JD_PRIMARY",
"enterprise_root": "ENTERPRISE_FOUR_DOMAIN_GUANGHU_OS_RUNTIME_ON_ENTERPRISE_ROOT_SERVER",
"linux_role": "SUBORDINATE_HARDWARE_SERVICE_AND_RESCUE_BRIDGE",
"ordinary_user_node_requires_full_os_install": false
}
}

View file

@ -12,6 +12,8 @@
"transport_is_authority": false
},
"mcp_role": "OPTIONAL_EXTERNAL_TOOL_ADAPTER_NOT_CONTINUITY_OR_AUTHORITY_ROOT",
"nearby_ai_discovery_contract": "contracts/nearby-ai-discovery.json",
"circular_lake_membrane_contract": "contracts/circular-lake-membrane.json",
"external_ai_entry": {
"mcp_may_bootstrap_discovery": true,
"direct_local_protocol_preferred_after_discovery": true,
@ -64,6 +66,9 @@
"cursor_is_bound_to_subject_object_version_and_query": true
},
"security": {
"protocol_external_input_discarded_before_language_runtime": true,
"visitor_natural_language_is_expression_only": true,
"visitor_session_has_system_authority": false,
"raw_account_identifier_in_storage_path": false,
"caller_selected_bridge_storage_root": false,
"credentials_exposed_to_programming_ai": false,

View file

@ -0,0 +1,33 @@
{
"schema": "hololake.nearby-ai-discovery-contract/v1",
"record_id": "HLP-NEARBY-AI-DISCOVERY-001",
"product_name": "光湖近场连接",
"principle": "DISCOVERY_IS_NOT_AUTHORIZATION",
"same_device": {
"auto_discovery": true,
"descriptor": "STANDARD_APP_DATA_DESCRIPTOR",
"transport": "USER_ONLY_UNIX_SOCKET",
"copy_large_invitation_required": false,
"network_required": false
},
"connection_modes": {
"GENERIC_AI_VISITOR": {
"state": "EXPRESSION_ONLY_READY",
"automatic_local_session": true,
"private_reads": false,
"tool_calls": false,
"execution_authority": false
},
"GUANGHU_PERSONA": {
"state": "BINDING_EVIDENCE_REQUIRED",
"automatic_identity_claim_allowed": false,
"visitor_session_may_upgrade": false
}
},
"local_network": {
"state": "DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL",
"mdns_advertisement_active": false,
"unauthenticated_tcp_listener_active": false
},
"mcp_role": "OPTIONAL_DISCOVERY_RECOVERY_COMPATIBILITY_ADAPTER"
}

View file

@ -42,7 +42,11 @@
"HUMAN_APPROVAL_CENTER",
"NATIVE_TRUST_BOUNDARY",
"LOCAL_DEVELOPMENT_BRIDGE",
"USER_CODE_CHANNEL"
"USER_CODE_CHANNEL",
"FIVE_DOMAIN_NUMBER_ROUTER",
"CIRCULAR_LAKE_PROTOCOL_MEMBRANE",
"NEARBY_AI_DISCOVERY",
"USER_NATIVE_GH_PNCC_CHANNEL"
,"PERSONAL_NODE_WORK_LAKE_AND_MOBILE_BRIDGE"
],
"human_surface": ["PERSONAL_CHANNEL_HOME", "MY_HOLOLAKE_OVERVIEW", "KNOWLEDGE_WORKSPACE", "USER_CODE_CHANNELS", "LOCAL_RECEIPTS", "SYSTEM_DETAILS", "HUMAN_APPROVAL_CENTER"],
@ -121,6 +125,6 @@
"mobile_is_same_persona_system_remote_body": true,
"enterprise_server_role": "MINIMUM_ACCOUNT_NUMBER_AND_NODE_VALIDITY_VERIFIER",
"knowledge_projection_is_authority_source": false,
"five_domain_primary_navigation_visible": false,
"five_domain_primary_navigation_visible": true,
"implementation_complete": false
}

View file

@ -0,0 +1,29 @@
{
"schema": "hololake.user-pncc-channel-contract/v1",
"record_id": "HLP-USER-PNCC-001",
"formal_name": "GH-PNCC · 光湖人格原生代码频道",
"state": "LOCAL_NATIVE_CHANNEL_IMPLEMENTED_REMOTE_PUBLICATION_UNBOUND",
"engine": "GIT",
"human_projection": "HOLOLAKE_NATIVE",
"forgejo_role": "OPTIONAL_REMOTE_COLLABORATION_ADAPTER",
"binding": {
"domain_resolved_before_login": true,
"domain_specific_login_and_node_entry_required": true,
"requires_verified_user_number": true,
"requires_authenticated_repository_account": true,
"password_written_to_repository": false,
"persona_binding_claimed": false
},
"local_repository": {
"automatic_idempotent_initialization": true,
"default_branch": "main",
"app_owned_private_root": true,
"initial_commit_receipt": true
},
"remote_repository": {
"created_automatically": false,
"bound": false,
"reason": "REMOTE_REPOSITORY_NAME_POLICY_AND_EXPLICIT_PUBLICATION_RECEIPT_NOT_REGISTERED"
},
"authority": "LOCAL_USER_CODE_CHANNEL_NO_PUSH_DEPLOY_OR_REALITY_EXECUTION_AUTHORITY"
}

View file

@ -1,10 +1,10 @@
# HoloLake Native Desktop Architecture
The stage-one shell is a Tauri v2 application with a Rust-owned local core and a React human projection. The default product surface is **My HoloLake**: overview, knowledge, user code channels, local receipts and system details. The five domains and their server fleet remain submerged system infrastructure rather than primary stage-one navigation.
The stage-one shell is a Tauri v2 application with a Rust-owned local core and a React human projection. Before authentication, its public surface is the five-domain number entrance. After verified routing and domain login, the default product surface becomes **My HoloLake**: overview, knowledge, user code channels, local receipts and system details. Domain servers and private registries remain submerged infrastructure.
## Stage-one human projection
The home surface follows the verified GHS-014 five-lakes visual grammar while applying the current stage-one correction: it is a restrained operating-system workspace, not a slogan page or a five-domain gate. The five named lake themes are token groups only and cannot change layout, copy, routing or authority. Internal transport and release details live under system details rather than dominating the first screen.
The public home surface follows the verified GHS-014 five-lakes visual grammar and presents the five domains as the system entrance. A user does not choose a domain manually: the submitted Guanghu number is resolved and validated first, and only then does HoloLake reveal the login surface belonging to that domain. Domain presentation cannot bypass registry isolation, and internal transport or release details remain under system details rather than dominating the first screen.
The first visible body uses a Rust-owned SQLite kernel under the Tauri app-data directory. A human-confirmed local display name creates one stable local subject and channel exactly once. The internal task/event/receipt kernel remains available for structured agents, but manual task title and purpose fields are not part of the default human surface. Event and receipt chains remain independently SHA-256-linked and fully revalidated before every read or mutation. A local identity is not platform authentication and grants no repository, node, server or deployment authority.
@ -26,10 +26,30 @@ A human may paste a registered Guanghu HTTPS code-channel address or select an e
This product channel is distinct from the under-lake PNCC persona-evidence projection below. It grants local source access only and never grants push, publication, deployment or server authority.
## Public five-domain number routing
The public HoloLake entry shows all five domain vestibules before authentication. A user does not select an authority-bearing domain manually. The submitted user number is sent to the registered internal router, which must return the exact canonical number, a positive registry verdict and one known domain. Only then may HoloLake load that domain's separately registered account and node entry.
The fifth-domain number registry belongs to the private fifth-domain system and is maintained only through its authorized registration path. The four enterprise-domain registries belong on the enterprise root server. Number syntax, a client-supplied domain, a generic successful response or a repository login cannot replace this routing proof. Missing and unavailable enterprise routes fail closed before login.
The fifth-domain root and the future enterprise root both run domain-specific Guanghu OS server runtimes. They are parallel bodies with different controllers, manifests, repositories and responsibility. A Linux host may remain underneath as the subordinate hardware, service and rescue bridge. Ordinary user computers and user-owned remote nodes require only HoloLake and the controlled node runtime, not a replacement operating-system installation.
## GH-PNCC user-native channel
HoloLake 0.4.0 adds the first native user-owned GH-PNCC vertical slice. After the Rust core has resolved a known domain, verified the user number at that domain's registered source and authenticated the account through that domain's entry, it derives a stable opaque repository id and idempotently creates or restores one private application-owned Git repository. The initial committed manifest records the domain, user number, account identity, engine and authority boundary. Repository credentials and passwords are never written to the Git tree or binding record.
The visible shell is HoloLake itself. Git is the durable history engine below it. Forgejo is an optional remote collaboration adapter rather than the product shell, identity kernel or persona. The current slice proves the local repository, initial commit, stable user binding and native browsing projection. It intentionally does not create a remote Forgejo repository, configure a remote, push code, claim persona binding, or grant publication, deployment or reality-execution authority; those actions require a separately registered naming, consent and receipt contract.
## External programming AI entry
MCP may discover HoloLake, but it does not own continuity. The installed application starts a user-only Unix socket broker. A programming AI opens or resumes a HoloLake-issued local session, then uses the installed executable's `--connector` mode for newline-delimited protocol traffic. Session secrets are stored only as hashes. Events use exact cursors and idempotency keys.
## Circular-lake protocol membrane and nearby AI
HoloLake 0.4.0 places a deterministic protocol membrane in front of the local language inbox. The membrane accepts only strict GLP/1.0 expression envelopes from a HoloLake-issued visitor session. Unknown fields, malformed identifiers, incorrect checksums, oversized content, attachments and command content are rejected before storage. Accepted natural language is an expression receipt only; it never carries execution authority by itself. Intent interpretation remains behind the membrane and cannot weaken its structural admission rules.
External AI on the same computer can discover the running HoloLake broker from a standard application-data descriptor and connect through a user-only Unix socket, without copying a long invitation string. A generic AI receives an expression-only visitor lane. A Guanghu persona connection remains unavailable until separate persona-binding evidence exists. Local-network discovery is deliberately deferred until encrypted transport, explicit human approval, replay protection and revocation are implemented; HoloLake does not expose an unauthenticated TCP listener or advertise a service on the LAN in this release.
## Dynamic capability routing
HoloLake stores a signed node capability registry rather than fixed server slots. A candidate route must bind the exact human, persona, domain and capability. It is eligible only while a fresh node-signed health receipt reports the capability as ready. Selection is deterministic by priority and node id.

View file

@ -0,0 +1,24 @@
# ADR 0003: Public number routing precedes a HoloLake-owned GH-PNCC
- Status: accepted; public routing shell and local GH-PNCC slice implemented
- Date: 2026-08-16
## Context
HoloLake is a public product for five independent domains. The fifth domain is private, while the other four domains belong to the enterprise reality body. A common client cannot ask every user to log in to the fifth-domain Forgejo, and it cannot infer authority from the visual shape of a number.
Each trusted user also needs one durable code channel bound to the registered number and account. Git already supplies the right history engine, but neither a generic Git browser nor a Forgejo page is the HoloLake product shell.
## Decision
The unauthenticated home shows the five public domain vestibules and one number entry. The user submits a number without choosing a domain. A registered internal router must resolve that number to one known domain and obtain an explicit verdict from the responsible registry. The fifth-domain registry is maintained inside the authorized fifth-domain system. The enterprise four-domain registries are served by the enterprise root server. Only a successful exact route may reveal the selected domain and load its own account and node login.
After domain routing, number verification and domain-specific account authentication, the Rust core derives a stable opaque repository id from the trusted tuple and idempotently creates or restores a private Git repository. HoloLake owns the human projection. Forgejo remains an optional remote collaboration adapter. Credentials never enter the repository, and local channel creation does not claim persona binding or remote authority.
## Server boundary
The enterprise root server runs an enterprise-domain Guanghu OS runtime, not a clone of the fifth-domain body. Its controller, manifests, repositories, registries and responsibility are independent. Linux may remain the subordinate hardware/service/rescue bridge. Ordinary personal nodes install the HoloLake node runtime rather than replacing their operating system.
## Current reality
Only the fifth-domain login adapter is currently provisioned. Enterprise cards are public and visible, but their account login remains fail-closed until an enterprise root server, signed route registration, number registries, node registration and domain handoff endpoints exist. The implementation must display this as unavailable, not simulate a successful login.

View file

@ -0,0 +1,37 @@
# ADR 0004: Circular-lake protocol membrane and nearby AI discovery
- Status: accepted for HoloLake 0.4.0
- Date: 2026-08-16
## Context
External AI needs a simple way to find HoloLake and deliver language without turning MCP, a copied connection ticket, an Agent framework or a model host into the product's authority root. A language-only boundary must also remain enforceable when the sender is malformed or adversarial; asking a persona to infer every sender's motive is neither deterministic nor a security boundary.
## Decision
The native Rust core owns a circular-lake membrane before the language inbox. It accepts a bounded, strict GLP/1.0 expression envelope only after HoloLake issues an expression-only visitor session. Protocol-invalid input is rejected before persistence or semantic interpretation. Accepted language creates a receipt but no execution authority.
The first discovery scope is the same logged-in operating-system account on one computer. A standard application-data descriptor points to a Unix socket restricted to that user. Generic AI may open an expression-only visitor lane. A Guanghu persona route requires separate verified persona-binding evidence and is not implemented by relabelling a visitor.
Local-network discovery is not enabled in this slice. It requires an encrypted mutually authenticated transport, explicit human approval, expiry, replay protection, revocation and visible connection receipts before any mDNS-style advertisement or LAN listener may be introduced.
MCP remains a compatibility and recovery adapter. It is not continuity, identity, memory or execution authority.
## Why
This preserves the user's "round lake" idea at an engineering boundary: non-protocol traffic never reaches the language world, while valid language still remains language rather than executable permission. The same-device descriptor provides Wi-Fi-like discovery where the operating system already supplies a trustworthy user boundary. Deferring LAN broadcast avoids falsely treating physical proximity or discoverability as authorization.
## Rejected alternatives
- Exposing an unauthenticated TCP or mDNS service now: discovery would outpace transport security and consent.
- Letting natural-language intent classification replace structural validation: probabilistic interpretation cannot be the outer security boundary.
- Treating any accepted message as a command: expression and execution authority must remain separate.
- Making MCP or a third-party Agent framework the continuity owner: adapters are replaceable tools beneath HoloLake.
## Evidence
- `contracts/circular-lake-membrane.json`
- `contracts/nearby-ai-discovery.json`
- `src-tauri/src/circular_lake_membrane.rs`
- `src-tauri/src/direct_local_broker.rs`
- `scripts/circular-lake-membrane.test.mjs`

View file

@ -13,6 +13,12 @@
"zero_point_nucleus_boot_time_protocol_comparison": true,
"zero_point_nucleus_signed_payload_installation": false,
"zero_point_nucleus_is_persona": false,
"domain_number_routing_contract": "contracts/domain-number-routing.json",
"public_entry_is_five_domain_home": true,
"number_selects_domain_route_before_login": true,
"enterprise_four_domain_registry_location": "ENTERPRISE_ROOT_SERVER",
"enterprise_root_guanghu_os_runtime_required": true,
"enterprise_linux_role": "SUBORDINATE_HARDWARE_SERVICE_AND_RESCUE_BRIDGE",
"installed_product_capability_audit": "audit/stage-one-installed-product-capability-audit.json",
"product_ui_implementation_started": true,
"selected_visual_direction_present": true,
@ -40,8 +46,16 @@
"code_channel_credential_prompting_disabled": true,
"code_channel_push_or_deploy_authority_granted": false,
"code_channel_installed_runtime_acceptance": true,
"user_pncc_channel_contract": "contracts/user-pncc-channel.json",
"user_pncc_verified_domain_number_and_account_binding_implemented": true,
"user_pncc_embedded_git_initialization_implemented": true,
"user_pncc_hololake_native_projection_implemented": true,
"user_pncc_forgejo_role": "OPTIONAL_REMOTE_COLLABORATION_ADAPTER",
"user_pncc_remote_repository_binding_implemented": false,
"user_pncc_persona_binding_claimed": false,
"local_runtime_acceptance_receipt": "audit/local-runtime-acceptance-20260815.json",
"installed_desktop_path": "/Users/bingshuolingdianyuanhe/Desktop/HoloLake 第一阶段.app",
"domain_membrane_pncc_installed_acceptance_receipt": "audit/hololake-0.4.0-domain-membrane-pncc-installed-acceptance-20260816.json",
"installed_desktop_path": "/Applications/HoloLake.app",
"old_desktop_apps_recoverably_archived": true,
"public_developer_id_and_notarized_acceptance": false,
"stage_one_implementation_order": [
@ -90,6 +104,11 @@
"local_development_bridge_contract": "contracts/local-development-bridge.json",
"account_single_writer_kernel_implemented": true,
"external_local_broker_implemented": true,
"nearby_ai_same_device_auto_discovery_implemented": true,
"nearby_ai_local_network_discovery_implemented": false,
"circular_lake_deterministic_protocol_membrane_implemented": true,
"generic_ai_expression_only_visitor_session_implemented": true,
"guanghu_persona_nearby_binding_implemented": false,
"resumable_direct_local_session_kernel_implemented": true,
"dynamic_capability_routing_contract": "contracts/dynamic-capability-routing.json",
"dynamic_capability_registry_implemented": true,
@ -108,7 +127,7 @@
"pncc_jd_live_server_projection_repository_content_exposed": false,
"pncc_jd_live_server_projection_write_authority": false,
"pncc_jd_live_server_projection_carrier_state": "UNBOUND_EVIDENCE_REQUIRED",
"installed_local_product_version": "0.3.0",
"installed_local_product_version": "0.4.0",
"pncc_authenticated_direct_broker_integration_implemented": true,
"pncc_human_mount_registration_implemented": true,
"pncc_human_mount_registration_gate": "SATISFIED_NATIVE_FILE_PICKER_EXACT_CONFIRMATION",
@ -121,7 +140,7 @@
"stage_one_model_api_configuration": false,
"vendor_adapter_matrix_required": false,
"first_public_product": "GH-AIOS_GENERAL_AI_OPERATING_PLATFORM",
"visible_five_domain_navigation_in_stage_one": false,
"visible_five_domain_navigation_in_stage_one": true,
"donors": [
{
"path": "../hololake-platform",

View file

@ -1,12 +1,12 @@
{
"name": "hololake-native-desktop",
"version": "0.3.0",
"version": "0.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hololake-native-desktop",
"version": "0.3.0",
"version": "0.4.0",
"dependencies": {
"@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-process": "2.3.1",

View file

@ -1,7 +1,7 @@
{
"name": "hololake-native-desktop",
"private": true,
"version": "0.3.0",
"version": "0.4.0",
"type": "module",
"scripts": {
"dev": "vite",

View file

@ -0,0 +1,31 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const read = (relative) => fs.readFileSync(path.join(root, relative), 'utf8')
test('the circular lake membrane rejects protocol-external input before language runtime', () => {
const contract = JSON.parse(read('contracts/circular-lake-membrane.json'))
assert.equal(contract.default, 'DISCARD')
assert.equal(contract.protocol_external_input_reaches_persona_context, false)
assert.equal(contract.natural_language_grants_execution_authority, false)
assert.equal(contract.intent_inference_required_for_protocol_rejection, false)
assert.equal(contract.deterministic_membrane_before_persona_parser, true)
assert.deepEqual(contract.accepted_language_protocols, ['GLP/1.0'])
})
test('nearby discovery supports automatic local visitor discovery and a closed persona route', () => {
const contract = JSON.parse(read('contracts/nearby-ai-discovery.json'))
assert.equal(contract.same_device.auto_discovery, true)
assert.equal(contract.same_device.copy_large_invitation_required, false)
assert.equal(contract.connection_modes.GENERIC_AI_VISITOR.state, 'EXPRESSION_ONLY_READY')
assert.equal(contract.connection_modes.GUANGHU_PERSONA.state, 'BINDING_EVIDENCE_REQUIRED')
assert.equal(contract.local_network.state, 'DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL')
const broker = read('src-tauri/src/direct_local_broker.rs')
assert.match(broker, /DiscoverNearby/)
assert.match(broker, /OpenVisitorSession/)
assert.match(broker, /ReceiveLanguage/)
})

View file

@ -0,0 +1,41 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const read = (relative) => fs.readFileSync(path.join(root, relative), 'utf8')
test('public routing starts from five domains and lets the number select the verifier', () => {
const contract = JSON.parse(read('contracts/domain-number-routing.json'))
assert.equal(contract.public_entry, 'FIVE_DOMAIN_HOME')
assert.equal(contract.user_selects_domain_before_number, false)
assert.equal(contract.route_key, 'USER_NUMBER')
assert.equal(contract.routing.number_shape_is_authority, false)
assert.equal(contract.routing.client_supplied_domain_is_authority, false)
assert.equal(contract.routing.unknown_or_unavailable_route, 'FAIL_CLOSED_BEFORE_LOGIN')
assert.equal(contract.registries.FIFTH_DOMAIN.ownership, 'ICE-GL_INFINITY_PRIVATE_DOMAIN')
assert.equal(contract.registries.ENTERPRISE_FOUR_DOMAINS.ownership, 'TCS_0002_ENTERPRISE_REALITY_BODY')
})
test('the public shell shows five domains and login is domain-routed', () => {
const frontend = read('src/main.tsx')
const login = read('src-tauri/src/code_repo_login.rs')
const router = read('src-tauri/src/zero_point.rs')
for (const name of ['光湖主域', '光湖分域', '光湖零域', '光湖零感域', '第五域 · 光湖本源域']) {
assert.match(frontend, new RegExp(name.replace('·', '\\·')))
}
assert.match(frontend, /输入编号进入所属域/)
assert.match(login, /login_host_for_domain/)
assert.match(login, /DOMAIN_LOGIN_NOT_PROVISIONED/)
assert.match(router, /verified_user_route/)
assert.match(router, /FIFTH_DOMAIN.*MAIN_DOMAIN.*BRANCH_DOMAIN.*ZERO_DOMAIN.*ZERO_SENSE_DOMAIN/s)
})
test('enterprise root runs a domain-specific Guanghu OS runtime while Linux remains subordinate', () => {
const contract = JSON.parse(read('contracts/domain-number-routing.json'))
assert.equal(contract.server_runtime.enterprise_root, 'ENTERPRISE_FOUR_DOMAIN_GUANGHU_OS_RUNTIME_ON_ENTERPRISE_ROOT_SERVER')
assert.equal(contract.server_runtime.linux_role, 'SUBORDINATE_HARDWARE_SERVICE_AND_RESCUE_BRIDGE')
assert.equal(contract.server_runtime.ordinary_user_node_requires_full_os_install, false)
})

View file

@ -92,10 +92,10 @@ test('broadcast remains Tauri-compatible and carries HoloLake human metadata', (
)
})
test('stage one is an AI operating platform while five domains remain hidden foundation', () => {
test('public entry presents five domains while domain contents remain isolated', () => {
assert.equal(foundation.first_public_product, 'GH-AIOS_GENERAL_AI_OPERATING_PLATFORM')
assert.equal(foundation.visible_five_domain_navigation_in_stage_one, false)
assert.equal(stageOne.five_domain_primary_navigation_visible, false)
assert.equal(foundation.visible_five_domain_navigation_in_stage_one, true)
assert.equal(stageOne.five_domain_primary_navigation_visible, true)
assert.ok(stageOne.human_surface.includes('HUMAN_APPROVAL_CENTER'))
assert.ok(stageOne.modules.includes('EVENT_AND_LAKE_LAMP'))
})

View file

@ -0,0 +1,34 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const readText = (relative) => fs.readFileSync(path.join(root, relative), 'utf8')
const readJson = (relative) => JSON.parse(readText(relative))
test('the native user PNCC contract binds verified number, signed-in account and embedded Git', () => {
const contract = readJson('contracts/user-pncc-channel.json')
assert.equal(contract.engine, 'GIT')
assert.equal(contract.human_projection, 'HOLOLAKE_NATIVE')
assert.equal(contract.forgejo_role, 'OPTIONAL_REMOTE_COLLABORATION_ADAPTER')
assert.equal(contract.binding.requires_verified_user_number, true)
assert.equal(contract.binding.domain_resolved_before_login, true)
assert.equal(contract.binding.domain_specific_login_and_node_entry_required, true)
assert.equal(contract.binding.requires_authenticated_repository_account, true)
assert.equal(contract.binding.password_written_to_repository, false)
assert.equal(contract.binding.persona_binding_claimed, false)
assert.equal(contract.local_repository.automatic_idempotent_initialization, true)
})
test('the user PNCC is a native command and first-class HoloLake projection', () => {
const native = readText('src-tauri/src/lib.rs')
const frontend = readText('src/main.tsx')
assert.match(native, /user_pncc_channel::get_user_pncc_channel/)
assert.match(native, /user_pncc_channel::ensure_user_pncc_channel/)
assert.match(frontend, /GH-PNCC · 人格原生代码频道/)
assert.match(frontend, /Forgejo 协作适配器/)
assert.match(frontend, /ensure_user_pncc_channel/)
assert.doesNotMatch(frontend, /Forgejo 页面/)
})

View file

@ -1458,7 +1458,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hololake-native-desktop"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"base64 0.22.1",
"dirs",

View file

@ -1,6 +1,6 @@
[package]
name = "hololake-native-desktop"
version = "0.3.0"
version = "0.4.0"
description = "HoloLake native desktop foundation"
authors = ["HoloLake"]
license = "AGPL-3.0-or-later"

View file

@ -0,0 +1,260 @@
//! 圆湖协议膜:协议外输入在进入人格或语言运行时之前确定性丢弃。
//!
//! 本模块只接收通过本机访客会话认证的 GLP/1.0 语言信封。合法自然语言只有表达权,
//! 不获得工具、代码、仓库、系统或现实执行权限。人格体连接继续等待独立绑定证据。
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
use crate::direct_local_session::{authenticate_with_lane_at, AuthenticateSessionInput};
use crate::glp_envelope::{payload_checksum, validate_external_language_envelope, GlpMessage};
const INBOX_SCHEMA: &str = "hololake.circular-lake-language-inbox/v1";
const RECEIPT_SCHEMA: &str = "hololake.circular-lake-ingress-receipt/v1";
const MAX_INBOX_MESSAGES: usize = 1_000;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ReceiveLanguageInput {
pub session: AuthenticateSessionInput,
pub envelope: GlpMessage,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CircularLakeIngressReceipt {
pub schema: &'static str,
pub state: &'static str,
pub message_id: String,
pub content_sha256: String,
pub received_at_unix_ms: u128,
pub expression_only: bool,
pub execution_authority: bool,
pub receipt_id: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct InboxRecord<'a> {
schema: &'static str,
state: &'static str,
connection_mode: &'static str,
received_at_unix_ms: u128,
expression_only: bool,
execution_authority: bool,
envelope: &'a GlpMessage,
}
pub(crate) fn receive_at(
inbox_root: &Path,
session_root: &Path,
input: ReceiveLanguageInput,
) -> Result<CircularLakeIngressReceipt, String> {
let lane = authenticate_with_lane_at(session_root, &input.session)?;
if lane != "visitor-expression-only" {
return Err("HOLOLAKE_CIRCULAR_LAKE_CONNECTION_MODE_MISMATCH".into());
}
validate_external_language_envelope(&input.envelope)?;
ensure_private_inbox(inbox_root)?;
if inbox_message_count(inbox_root)? >= MAX_INBOX_MESSAGES {
return Err("HOLOLAKE_CIRCULAR_LAKE_INBOX_CAPACITY_REACHED".into());
}
let received_at_unix_ms = now_unix_ms()?;
let content_sha256 = payload_checksum(&input.envelope.payload.content);
let receipt_id = format!("lake-receipt-{}", Uuid::new_v4());
let record = InboxRecord {
schema: INBOX_SCHEMA,
state: "ACCEPTED_TO_LANGUAGE_INBOX",
connection_mode: "GENERIC_AI_VISITOR",
received_at_unix_ms,
expression_only: true,
execution_authority: false,
envelope: &input.envelope,
};
let path = inbox_root.join(format!("{received_at_unix_ms}-{receipt_id}.json"));
write_private_create_new(&path, &record)?;
Ok(CircularLakeIngressReceipt {
schema: RECEIPT_SCHEMA,
state: "ACCEPTED_EXPRESSION_ONLY",
message_id: input.envelope.message_id,
content_sha256,
received_at_unix_ms,
expression_only: true,
execution_authority: false,
receipt_id,
})
}
fn ensure_private_inbox(root: &Path) -> Result<(), String> {
fs::create_dir_all(root)
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_INBOX_UNAVAILABLE: {error}"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(root, fs::Permissions::from_mode(0o700))
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_INBOX_PERMISSION_FAILED: {error}"))?;
}
Ok(())
}
fn inbox_message_count(root: &Path) -> Result<usize, String> {
Ok(fs::read_dir(root)
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_INBOX_UNAVAILABLE: {error}"))?
.filter_map(Result::ok)
.filter(|entry| {
entry
.file_type()
.map(|kind| kind.is_file())
.unwrap_or(false)
&& entry.path().extension().and_then(|value| value.to_str()) == Some("json")
})
.count())
}
fn write_private_create_new<T: Serialize>(path: &Path, value: &T) -> Result<(), String> {
let bytes = serde_json::to_vec_pretty(value)
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_RECORD_INVALID: {error}"))?;
let mut options = OpenOptions::new();
options.create_new(true).write(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options
.open(path)
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_INBOX_WRITE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.sync_all())
.map_err(|error| format!("HOLOLAKE_CIRCULAR_LAKE_INBOX_WRITE_FAILED: {error}"))
}
fn now_unix_ms() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.map_err(|error| format!("HOLOLAKE_CLOCK_INVALID: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::direct_local_session::{
issue_ticket_at, open_at, IssueDiscoveryTicketInput, OpenSessionInput,
};
use crate::glp_envelope::{
ContentType, GlpContext, GlpControl, GlpIntegrity, GlpPayload, GlpReceiver, GlpSender,
Priority, RetryPolicy, RoutingMode,
};
use tempfile::tempdir;
fn visitor_session(root: &Path) -> AuthenticateSessionInput {
let account_id = "visitor:test-ai".to_string();
let ticket = issue_ticket_at(
root,
IssueDiscoveryTicketInput {
account_id: account_id.clone(),
lane_id: "visitor-expression-only".into(),
client_instance_id: "test-ai".into(),
},
)
.unwrap();
let opened = open_at(
root,
OpenSessionInput {
account_id: account_id.clone(),
lane_id: "visitor-expression-only".into(),
client_instance_id: "test-ai".into(),
discovery_ticket: ticket.discovery_ticket,
},
)
.unwrap();
AuthenticateSessionInput {
account_id,
session_id: opened.session_id,
resume_secret: opened.resume_secret.unwrap(),
}
}
fn envelope(content: &str) -> GlpMessage {
GlpMessage {
protocol: "GLP/1.0".into(),
message_id: "GLP-MSG-20260816-000001".into(),
message_type: "DIRECT".into(),
created_at: "2026-08-16T00:00:00Z".into(),
sender: GlpSender {
object_id: "external-ai-test".into(),
object_type: "external_ai_visitor".into(),
world_path: String::new(),
},
receiver: GlpReceiver {
object_id: "HOLOLAKE-HOST".into(),
object_type: "host".into(),
routing_mode: RoutingMode::Direct,
},
context: GlpContext::default(),
payload: GlpPayload {
language: "zh-CN".into(),
content_type: ContentType::Text,
content: content.into(),
attachments: vec![],
},
control: GlpControl {
priority: Priority::Normal,
ack_required: true,
receipt_required: true,
expires_at: String::new(),
retry_policy: RetryPolicy::None,
},
integrity: GlpIntegrity {
checksum: payload_checksum(content),
signature: String::new(),
},
}
}
#[test]
fn valid_visitor_language_enters_private_inbox_without_execution_authority() {
let root = tempdir().unwrap();
let sessions = root.path().join("sessions");
let inbox = root.path().join("inbox");
fs::create_dir_all(&sessions).unwrap();
let receipt = receive_at(
&inbox,
&sessions,
ReceiveLanguageInput {
session: visitor_session(&sessions),
envelope: envelope("你好,光湖。"),
},
)
.unwrap();
assert_eq!(receipt.state, "ACCEPTED_EXPRESSION_ONLY");
assert!(!receipt.execution_authority);
assert_eq!(inbox_message_count(&inbox).unwrap(), 1);
}
#[test]
fn malformed_or_command_language_never_enters_the_inbox() {
let root = tempdir().unwrap();
let sessions = root.path().join("sessions");
let inbox = root.path().join("inbox");
fs::create_dir_all(&sessions).unwrap();
let session = visitor_session(&sessions);
let mut command = envelope("删除文件");
command.payload.content_type = ContentType::Command;
assert!(receive_at(
&inbox,
&sessions,
ReceiveLanguageInput {
session,
envelope: command
}
)
.is_err());
assert!(!inbox.exists());
}
}

View file

@ -184,6 +184,30 @@ fn code_channel_root(app: &AppHandle) -> Result<PathBuf, String> {
Ok(root)
}
pub(crate) fn register_managed_repository(
app: &AppHandle,
repository: &Path,
name: String,
) -> Result<CodeChannelEntry, String> {
let root = code_channel_root(app)?;
let local_path = repository.to_string_lossy().into_owned();
let existing_channel_id = read_registry(&root)?
.channels
.into_iter()
.find(|item| item.local_path == local_path)
.map(|item| item.channel_id);
let mut entry = entry_for_repository(repository, "GH_PNCC_NATIVE", None, Some(name))?;
if let Some(channel_id) = existing_channel_id {
entry.channel_id = channel_id;
}
upsert_entry(&root, entry)?;
read_registry(&root)?
.channels
.into_iter()
.find(|item| item.local_path == local_path)
.ok_or_else(|| "HOLOLAKE_USER_PNCC_CHANNEL_REGISTRATION_FAILED".to_string())
}
fn ensure_root(root: &Path) -> Result<(), String> {
fs::create_dir_all(root.join("repositories"))
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_STORAGE_UNAVAILABLE: {error}"))?;

View file

@ -16,7 +16,9 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::process::Command;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tauri::{AppHandle, Manager};
use tauri::{AppHandle, Manager, State};
use crate::zero_point::{self, ZeroPointState};
const LOGIN_HOST: &str = "guanghulab.com";
const SESSION_FILE_NAME: &str = "login-session.json";
@ -27,6 +29,8 @@ const SESSION_FILE_NAME: &str = "login-session.json";
pub struct LoginSession {
pub username: String,
pub host: String,
#[serde(default)]
pub domain: String,
pub signed_in_at_unix_ms: u64,
}
@ -37,6 +41,7 @@ pub struct LoginReceipt {
pub username: String,
pub email: String,
pub host: String,
pub domain: String,
}
fn session_path(app: &AppHandle) -> Result<std::path::PathBuf, String> {
@ -115,14 +120,24 @@ fn validate_username(raw: &str) -> Result<String, String> {
/// 启动时自查:本机有没有已登录会话(会话文件在,且钥匙串里凭证还在)。
#[tauri::command]
pub fn check_code_repo_login(app: AppHandle) -> Result<Option<LoginSession>, String> {
let path = session_path(&app)?;
pub fn check_code_repo_login(
app: AppHandle,
state: State<'_, ZeroPointState>,
) -> Result<Option<LoginSession>, String> {
let Some((_, domain)) = zero_point::verified_user_route(&state)? else {
return Ok(None);
};
current_login_session_for_domain(&app, &domain)
}
pub(crate) fn current_login_session(app: &AppHandle) -> Result<Option<LoginSession>, String> {
let path = session_path(app)?;
let raw = match fs::read_to_string(&path) {
Ok(raw) => raw,
Err(_) => return Ok(None),
};
let session: LoginSession = serde_json::from_str(&raw)
.map_err(|_| "HOLOLAKE_LOGIN_SESSION_CORRUPT".to_string())?;
let session: LoginSession =
serde_json::from_str(&raw).map_err(|_| "HOLOLAKE_LOGIN_SESSION_CORRUPT".to_string())?;
if keychain_has(&session.host, &session.username) {
Ok(Some(session))
} else {
@ -131,14 +146,47 @@ pub fn check_code_repo_login(app: AppHandle) -> Result<Option<LoginSession>, Str
}
}
pub(crate) fn current_login_session_for_domain(
app: &AppHandle,
domain: &str,
) -> Result<Option<LoginSession>, String> {
let Some(mut session) = current_login_session(app)? else {
return Ok(None);
};
// 0.3.0 的会话只可能来自第五域固定入口;仅在第五域内兼容迁移。
if session.domain.is_empty() && session.host == LOGIN_HOST && domain == "FIFTH_DOMAIN" {
session.domain = domain.into();
}
if session.domain == domain && login_host_for_domain(domain)? == session.host {
Ok(Some(session))
} else {
Ok(None)
}
}
fn login_host_for_domain(domain: &str) -> Result<&'static str, String> {
match domain {
"FIFTH_DOMAIN" => Ok(LOGIN_HOST),
"MAIN_DOMAIN" | "BRANCH_DOMAIN" | "ZERO_DOMAIN" | "ZERO_SENSE_DOMAIN" => {
Err("HOLOLAKE_DOMAIN_LOGIN_NOT_PROVISIONED".into())
}
_ => Err("HOLOLAKE_DOMAIN_ROUTE_INVALID".into()),
}
}
/// 登录验证:基本认证打 Forgejo 用户接口,密码错=401 即拒;
/// 通过后凭证进钥匙串,会话(不含密码)落盘。
#[tauri::command]
pub async fn perform_code_repo_login(
app: AppHandle,
state: State<'_, ZeroPointState>,
username: String,
password: String,
) -> Result<LoginReceipt, String> {
let Some((_, domain)) = zero_point::verified_user_route(&state)? else {
return Err("HOLOLAKE_DOMAIN_ROUTE_REQUIRED".into());
};
let login_host = login_host_for_domain(&domain)?;
let username = validate_username(&username)?;
if password.is_empty() || password.len() > 512 {
return Err("HOLOLAKE_LOGIN_CREDENTIALS_INVALID".into());
@ -148,7 +196,7 @@ pub async fn perform_code_repo_login(
.use_rustls_tls()
.build()
.map_err(|error| format!("HOLOLAKE_LOGIN_NETWORK_FAILED: {error}"))?;
let url = format!("https://{LOGIN_HOST}/code/api/v1/user");
let url = format!("https://{login_host}/code/api/v1/user");
let response = client
.get(&url)
.basic_auth(&username, Some(&password))
@ -175,10 +223,11 @@ pub async fn perform_code_repo_login(
.and_then(|value| value.as_str())
.unwrap_or("")
.to_string();
keychain_store(LOGIN_HOST, &username, &password)?;
keychain_store(login_host, confirmed_login, &password)?;
let session = LoginSession {
username: confirmed_login.to_string(),
host: LOGIN_HOST.to_string(),
host: login_host.to_string(),
domain: domain.clone(),
signed_in_at_unix_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis() as u64)
@ -198,7 +247,8 @@ pub async fn perform_code_repo_login(
Ok(LoginReceipt {
username: confirmed_login.to_string(),
email,
host: LOGIN_HOST.to_string(),
host: login_host.to_string(),
domain,
})
}
@ -214,3 +264,28 @@ pub fn sign_out_code_repo_login(app: AppHandle) -> Result<(), String> {
let _ = fs::remove_file(&path);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_registered_domain_login_host_can_be_selected() {
assert_eq!(login_host_for_domain("FIFTH_DOMAIN").unwrap(), LOGIN_HOST);
for domain in [
"MAIN_DOMAIN",
"BRANCH_DOMAIN",
"ZERO_DOMAIN",
"ZERO_SENSE_DOMAIN",
] {
assert_eq!(
login_host_for_domain(domain).unwrap_err(),
"HOLOLAKE_DOMAIN_LOGIN_NOT_PROVISIONED"
);
}
assert_eq!(
login_host_for_domain("UNKNOWN").unwrap_err(),
"HOLOLAKE_DOMAIN_ROUTE_INVALID"
);
}
}

View file

@ -1,6 +1,8 @@
use crate::circular_lake_membrane::{receive_at as receive_language_at, ReceiveLanguageInput};
use crate::direct_local_session::{
append_event_at, authenticate_at, direct_session_root, open_at, resume_at,
AppendSessionEventInput, AuthenticateSessionInput, OpenSessionInput, ResumeSessionInput,
append_event_at, authenticate_privileged_at, direct_session_root, issue_ticket_at, open_at,
resume_at, AppendSessionEventInput, AuthenticateSessionInput, DirectSessionReceipt,
IssueDiscoveryTicketInput, OpenSessionInput, ResumeSessionInput,
};
use crate::dynamic_capability_routing::{
install_trusted_registry_at, record_health_at, resolve_at as resolve_capability_route_at,
@ -36,7 +38,8 @@ use tauri::{AppHandle, Manager};
use uuid::Uuid;
const BROKER_SCHEMA: &str = "hololake.direct-local-broker/v1";
const MAX_REQUEST_BYTES: u64 = 1024 * 1024;
const MAX_REQUEST_BYTES: u64 = 128 * 1024;
const DISCOVERY_SCHEMA: &str = "hololake.nearby-ai-discovery/v1";
pub struct DirectLocalBrokerHandle {
shutdown: Arc<AtomicBool>,
@ -59,12 +62,14 @@ impl Drop for AuthenticatedConnectionGuard {
}
}
#[derive(Clone)]
struct BrokerStorageRoots {
session: PathBuf,
routing: PathBuf,
pncc_mount: PathBuf,
pncc_remote: PathBuf,
pncc_projection: PathBuf,
language_inbox: PathBuf,
}
impl Drop for DirectLocalBrokerHandle {
@ -81,9 +86,13 @@ impl Drop for DirectLocalBrokerHandle {
#[serde(
tag = "operation",
content = "input",
rename_all = "SCREAMING_SNAKE_CASE"
rename_all = "SCREAMING_SNAKE_CASE",
deny_unknown_fields
)]
enum BrokerRequest {
DiscoverNearby,
OpenVisitorSession(OpenVisitorSessionInput),
ReceiveLanguage(Box<ReceiveLanguageInput>),
Ping,
OpenSession(OpenSessionInput),
ResumeSession(ResumeSessionInput),
@ -96,6 +105,40 @@ enum BrokerRequest {
QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput),
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct OpenVisitorSessionInput {
client_instance_id: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct OpenVisitorSessionReceipt {
schema: &'static str,
state: &'static str,
connection_mode: &'static str,
account_id: String,
session: DirectSessionReceipt,
expression_only: bool,
execution_authority: bool,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NearbyAiDiscoverySnapshot {
pub schema: &'static str,
pub state: &'static str,
pub service_name: &'static str,
pub transport: &'static str,
pub language_protocol: &'static str,
pub automatic_same_device_discovery: bool,
pub large_invitation_copy_required: bool,
pub generic_ai_visitor: &'static str,
pub guanghu_persona: &'static str,
pub local_network_discovery: &'static str,
pub authority: &'static str,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct AuthenticatedRouteInput {
@ -157,6 +200,7 @@ struct BrokerDescriptor {
socket_path: String,
max_request_bytes: u64,
process_id: u32,
discovery: NearbyAiDiscoverySnapshot,
}
#[derive(Debug, Deserialize)]
@ -178,6 +222,27 @@ pub fn start(app: &AppHandle) -> Result<DirectLocalBrokerHandle, Box<dyn std::er
start_at(session_root, routing_root, descriptor_path, socket_path).map_err(|error| error.into())
}
#[tauri::command]
pub fn get_nearby_ai_discovery() -> NearbyAiDiscoverySnapshot {
nearby_discovery_snapshot()
}
fn nearby_discovery_snapshot() -> NearbyAiDiscoverySnapshot {
NearbyAiDiscoverySnapshot {
schema: DISCOVERY_SCHEMA,
state: "DISCOVERABLE_ON_SAME_DEVICE",
service_name: "HoloLake",
transport: "STANDARD_APP_DATA_DESCRIPTOR_TO_USER_ONLY_UNIX_SOCKET",
language_protocol: "GLP/1.0",
automatic_same_device_discovery: true,
large_invitation_copy_required: false,
generic_ai_visitor: "EXPRESSION_ONLY_READY",
guanghu_persona: "BINDING_EVIDENCE_REQUIRED",
local_network_discovery: "DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL",
authority: "DISCOVERY_IS_NOT_AUTHORIZATION",
}
}
pub fn run_connector() -> Result<(), String> {
let descriptor_path = connector_descriptor_path()?;
let descriptor: ConnectorDescriptor = serde_json::from_slice(
@ -284,6 +349,10 @@ fn start_at(
.join("receipt-projection");
fs::create_dir_all(&pncc_projection_root)
.map_err(|error| format!("HOLOLAKE_BROKER_PNCC_STORAGE_UNAVAILABLE: {error}"))?;
let language_inbox_root = session_root
.parent()
.ok_or("HOLOLAKE_BROKER_LANGUAGE_STORAGE_BOUNDARY_INVALID")?
.join("circular-lake-language-inbox-v1");
if let Some(parent) = socket_path.parent() {
fs::create_dir_all(parent)
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?;
@ -315,6 +384,7 @@ fn start_at(
socket_path: socket_path.to_string_lossy().into_owned(),
max_request_bytes: MAX_REQUEST_BYTES,
process_id: std::process::id(),
discovery: nearby_discovery_snapshot(),
},
)?;
@ -336,6 +406,7 @@ fn start_at(
pncc_mount: pncc_mount_root,
pncc_remote: pncc_remote_root,
pncc_projection: pncc_projection_root,
language_inbox: language_inbox_root,
},
&worker_shutdown,
&worker_authenticated_connections,
@ -370,24 +441,12 @@ fn serve(
if stream.set_nonblocking(false).is_err() {
continue;
}
let root = roots.session.clone();
let routes = roots.routing.clone();
let pncc_mounts = roots.pncc_mount.clone();
let pncc_remote = roots.pncc_remote.clone();
let pncc_projection = roots.pncc_projection.clone();
let client_roots = roots.clone();
let client_authenticated_connections = Arc::clone(authenticated_connections);
let _ = thread::Builder::new()
.name("hololake-direct-local-client".into())
.spawn(move || {
serve_connection(
stream,
&root,
&routes,
&pncc_mounts,
&pncc_remote,
&pncc_projection,
client_authenticated_connections,
)
serve_connection(stream, &client_roots, client_authenticated_connections)
});
}
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
@ -400,11 +459,7 @@ fn serve(
fn serve_connection(
mut stream: UnixStream,
session_root: &Path,
routing_root: &Path,
pncc_mount_root: &Path,
pncc_remote_root: &Path,
pncc_projection_root: &Path,
roots: &BrokerStorageRoots,
authenticated_connections: Arc<AtomicUsize>,
) {
let read_stream = match stream.try_clone() {
@ -431,11 +486,12 @@ fn serve_connection(
BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE")
} else {
dispatch(
session_root,
routing_root,
pncc_mount_root,
pncc_remote_root,
pncc_projection_root,
&roots.session,
&roots.routing,
&roots.pncc_mount,
&roots.pncc_remote,
&roots.pncc_projection,
&roots.language_inbox,
&bytes[..bytes.len() - 1],
)
};
@ -466,7 +522,12 @@ fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool {
.and_then(Value::as_str)
.map(str::to_owned)
})
.is_some_and(|operation| matches!(operation.as_str(), "OPEN_SESSION" | "RESUME_SESSION"))
.is_some_and(|operation| {
matches!(
operation.as_str(),
"OPEN_SESSION" | "RESUME_SESSION" | "OPEN_VISITOR_SESSION"
)
})
}
fn dispatch(
@ -475,6 +536,7 @@ fn dispatch(
pncc_mount_root: &Path,
pncc_remote_root: &Path,
pncc_projection_root: &Path,
language_inbox_root: &Path,
bytes: &[u8],
) -> BrokerResponse {
let request: BrokerRequest = match serde_json::from_slice(bytes) {
@ -484,6 +546,16 @@ fn dispatch(
}
};
let result = match request {
BrokerRequest::DiscoverNearby => {
serde_json::to_value(nearby_discovery_snapshot()).map_err(|error| error.to_string())
}
BrokerRequest::OpenVisitorSession(input) => open_visitor_session_at(session_root, input)
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::ReceiveLanguage(input) => {
receive_language_at(language_inbox_root, session_root, *input).and_then(|receipt| {
serde_json::to_value(receipt).map_err(|error| error.to_string())
})
}
BrokerRequest::Ping => serde_json::to_value(serde_json::json!({
"state": "READY",
"continuityOwner": "HOLOLAKE",
@ -497,7 +569,7 @@ fn dispatch(
BrokerRequest::AppendEvent(input) => append_event_at(session_root, input)
.and_then(|receipt| serde_json::to_value(receipt).map_err(|error| error.to_string())),
BrokerRequest::ResolveCapabilityRoute(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
let now = std::time::SystemTime::now()
@ -510,7 +582,7 @@ fn dispatch(
})
}
BrokerRequest::InstallDynamicNodeRegistry(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
install_trusted_registry_at(routing_root, input.registry).and_then(|receipt| {
@ -518,7 +590,7 @@ fn dispatch(
})
}
BrokerRequest::RecordSignedNodeHealth(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
record_health_at(routing_root, input.health).and_then(|receipt| {
@ -526,7 +598,7 @@ fn dispatch(
})
}
BrokerRequest::InspectMountedPnccRepository(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
inspect_mounted_pncc_at(pncc_mount_root, input.mount).and_then(|receipt| {
@ -535,7 +607,7 @@ fn dispatch(
})
}
BrokerRequest::ReadMountedPnccRemoteObject(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
read_mounted_pncc_remote_at(pncc_remote_root, input.read).and_then(|receipt| {
@ -544,7 +616,7 @@ fn dispatch(
})
}
BrokerRequest::QueryPnccReceiptProjection(input) => {
if let Err(error) = authenticate_at(session_root, &input.session) {
if let Err(error) = authenticate_privileged_at(session_root, &input.session) {
return BrokerResponse::error(&error);
}
query_pncc_projection_at(pncc_projection_root, input.query).and_then(|receipt| {
@ -558,6 +630,51 @@ fn dispatch(
}
}
fn open_visitor_session_at(
session_root: &Path,
input: OpenVisitorSessionInput,
) -> Result<OpenVisitorSessionReceipt, String> {
if input.client_instance_id.is_empty() || input.client_instance_id.len() > 128 {
return Err("HOLOLAKE_VISITOR_CLIENT_ID_INVALID".into());
}
let client_key = sha256_hex(input.client_instance_id.as_bytes());
let account_id = format!("visitor:{}", &client_key[..24]);
let ticket = issue_ticket_at(
session_root,
IssueDiscoveryTicketInput {
account_id: account_id.clone(),
lane_id: "visitor-expression-only".into(),
client_instance_id: input.client_instance_id.clone(),
},
)?;
let session = open_at(
session_root,
OpenSessionInput {
account_id: account_id.clone(),
lane_id: "visitor-expression-only".into(),
client_instance_id: input.client_instance_id,
discovery_ticket: ticket.discovery_ticket,
},
)?;
Ok(OpenVisitorSessionReceipt {
schema: "hololake.nearby-ai-visitor-session/v1",
state: "OPENED",
connection_mode: "GENERIC_AI_VISITOR",
account_id,
session,
expression_only: true,
execution_authority: false,
})
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
impl BrokerResponse {
fn success(result: Value) -> Self {
Self {
@ -956,4 +1073,81 @@ mod tests {
}
assert_eq!(broker.active_connection_count(), 0);
}
#[test]
fn nearby_visitor_is_auto_discoverable_expression_only_and_cannot_use_system_tools() {
let temp = TempDir::new().unwrap();
let socket = temp.path().join("runtime/broker.sock");
let descriptor = temp.path().join("broker.json");
let sessions = temp.path().join("sessions");
let routes = temp.path().join("routes");
fs::create_dir_all(&sessions).unwrap();
fs::create_dir_all(&routes).unwrap();
let _broker = start_at(sessions, routes, descriptor, socket.clone()).unwrap();
let discovered = request(
&socket,
serde_json::json!({ "operation": "DISCOVER_NEARBY" }),
);
assert_eq!(discovered["result"]["automaticSameDeviceDiscovery"], true);
assert_eq!(
discovered["result"]["genericAiVisitor"],
"EXPRESSION_ONLY_READY"
);
let opened = request(
&socket,
serde_json::json!({
"operation": "OPEN_VISITOR_SESSION",
"input": { "clientInstanceId": "outside-ai-test" }
}),
);
assert_eq!(opened["result"]["executionAuthority"], false);
let content = "你好,光湖。";
let accepted = request(
&socket,
serde_json::json!({
"operation": "RECEIVE_LANGUAGE",
"input": {
"session": {
"accountId": opened["result"]["accountId"],
"sessionId": opened["result"]["session"]["sessionId"],
"resumeSecret": opened["result"]["session"]["resumeSecret"]
},
"envelope": {
"protocol": "GLP/1.0",
"message_id": "GLP-MSG-20260816-000001",
"message_type": "DIRECT",
"created_at": "2026-08-16T00:00:00Z",
"sender": { "object_id": "outside-ai-test", "object_type": "external_ai_visitor", "world_path": "" },
"receiver": { "object_id": "HOLOLAKE-HOST", "object_type": "host", "routing_mode": "direct" },
"context": {},
"payload": { "language": "zh-CN", "content_type": "text", "content": content, "attachments": [] },
"control": { "priority": "normal", "ack_required": true, "receipt_required": true, "expires_at": "", "retry_policy": "none" },
"integrity": { "checksum": crate::glp_envelope::payload_checksum(content), "signature": "" }
}
}
}),
);
assert_eq!(accepted["result"]["state"], "ACCEPTED_EXPRESSION_ONLY");
let denied = request(
&socket,
serde_json::json!({
"operation": "QUERY_PNCC_RECEIPT_PROJECTION",
"input": {
"session": {
"accountId": opened["result"]["accountId"],
"sessionId": opened["result"]["session"]["sessionId"],
"resumeSecret": opened["result"]["session"]["resumeSecret"]
},
"query": { "afterSequence": 0, "limit": 1 }
}
}),
);
assert_eq!(
denied["error"],
"HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY"
);
}
}

View file

@ -442,7 +442,10 @@ pub(crate) fn append_event_at(
Ok(event_receipt("APPENDED", &event))
}
pub(crate) fn authenticate_at(root: &Path, input: &AuthenticateSessionInput) -> Result<(), String> {
pub(crate) fn authenticate_with_lane_at(
root: &Path,
input: &AuthenticateSessionInput,
) -> Result<String, String> {
validate_identifier(&input.account_id, "ACCOUNT")?;
validate_identifier(&input.session_id, "SESSION")?;
validate_secret(&input.resume_secret, "RESUME_SECRET")?;
@ -456,7 +459,20 @@ pub(crate) fn authenticate_at(root: &Path, input: &AuthenticateSessionInput) ->
&account_key,
&input.session_id,
&input.resume_secret,
)
)?;
Ok(record.lane_id)
}
pub(crate) fn authenticate_privileged_at(
root: &Path,
input: &AuthenticateSessionInput,
) -> Result<(), String> {
let lane = authenticate_with_lane_at(root, input)?;
if lane == "visitor-expression-only" {
Err("HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY".into())
} else {
Ok(())
}
}
fn session_path(root: &Path, account_key: &str, session_id: &str) -> PathBuf {

View file

@ -7,9 +7,13 @@
//! 指挥链落点(铁律四):人格体→宿主的一切指令都必须是这个信封;
//! 宿主只认信封不认散话。
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use std::time::{SystemTime, UNIX_EPOCH};
const MAX_LANGUAGE_CONTENT_BYTES: usize = 64 * 1024;
/// GLS-0300 · receiver.routing_mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@ -51,7 +55,7 @@ pub enum RetryPolicy {
/// GLS-0300 · sender
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpSender {
pub object_id: String,
pub object_type: String,
@ -61,7 +65,7 @@ pub struct GlpSender {
/// GLS-0300 · receiver
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpReceiver {
pub object_id: String,
pub object_type: String,
@ -70,7 +74,7 @@ pub struct GlpReceiver {
/// GLS-0300 · contexthldp_anchor 即铁律二的记忆锚点)
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpContext {
#[serde(default)]
pub conversation_id: String,
@ -86,7 +90,7 @@ pub struct GlpContext {
/// GLS-0300 · payload
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpPayload {
pub language: String,
pub content_type: ContentType,
@ -97,7 +101,7 @@ pub struct GlpPayload {
/// GLS-0300 · control
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpControl {
pub priority: Priority,
pub ack_required: bool,
@ -109,7 +113,7 @@ pub struct GlpControl {
/// GLS-0300 · integrity高风险消息须带签名与回执链——通信安全节
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpIntegrity {
#[serde(default)]
pub checksum: String,
@ -119,7 +123,7 @@ pub struct GlpIntegrity {
/// GLS-0300 · glp_message 全信封
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpMessage {
pub protocol: String,
pub message_id: String,
@ -139,7 +143,7 @@ pub fn validate_envelope(message: &GlpMessage) -> Result<(), String> {
if message.protocol != "GLP/1.0" {
return Err("HOLOLAKE_GLP_PROTOCOL_UNKNOWN".into());
}
if message.message_id.trim().is_empty() || message.created_at.trim().is_empty() {
if !valid_message_id(&message.message_id) || message.created_at.trim().is_empty() {
return Err("HOLOLAKE_GLP_ENVELOPE_INCOMPLETE".into());
}
if message.sender.object_id.trim().is_empty() || message.sender.object_type.trim().is_empty() {
@ -150,19 +154,66 @@ pub fn validate_envelope(message: &GlpMessage) -> Result<(), String> {
{
return Err("HOLOLAKE_GLP_RECEIVER_INCOMPLETE".into());
}
if message.payload.content.is_empty() {
if message.payload.content.is_empty()
|| message.payload.content.len() > MAX_LANGUAGE_CONTENT_BYTES
|| message.payload.language.len() > 32
|| message.payload.attachments.len() > 16
{
return Err("HOLOLAKE_GLP_PAYLOAD_EMPTY".into());
}
if !matches!(
message.message_type.as_str(),
"DIRECT" | "CHANNEL" | "BROADCAST"
) {
return Err("HOLOLAKE_GLP_MESSAGE_TYPE_INVALID".into());
}
Ok(())
}
/// 圆湖协议膜的外部入口校验:通信正文只能作为语言表达进入,不能夹带附件或执行权。
pub fn validate_external_language_envelope(message: &GlpMessage) -> Result<(), String> {
validate_envelope(message)?;
if message.receiver.object_id != "HOLOLAKE-HOST"
|| message.receiver.object_type != "host"
|| message.sender.object_type != "external_ai_visitor"
|| !message.payload.attachments.is_empty()
|| matches!(message.payload.content_type, ContentType::Command)
{
return Err("HOLOLAKE_CIRCULAR_LAKE_EXPRESSION_BOUNDARY_DENIED".into());
}
if message.integrity.checksum != payload_checksum(&message.payload.content) {
return Err("HOLOLAKE_CIRCULAR_LAKE_CHECKSUM_INVALID".into());
}
Ok(())
}
pub fn payload_checksum(content: &str) -> String {
digest(&SHA256, content.as_bytes())
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn valid_message_id(value: &str) -> bool {
let Some(suffix) = value.strip_prefix("GLP-MSG-") else {
return false;
};
let mut parts = suffix.split('-');
matches!(parts.next(), Some(date) if date.len() == 8 && date.bytes().all(|byte| byte.is_ascii_digit()))
&& matches!(parts.next(), Some(sequence) if sequence.len() == 6 && sequence.bytes().all(|byte| byte.is_ascii_digit()))
&& parts.next().is_none()
}
/// 消息编号按老家谱格式生成GLP-MSG-YYYYMMDD-000001。
/// 序号由账本(管家层)当日累计给出,这里只拼形状。
#[cfg(test)]
pub fn build_message_id(date_compact: &str, daily_sequence: u64) -> String {
format!("GLP-MSG-{date_compact}-{daily_sequence:06}")
}
/// ISO-8601 近似时刻戳秒级UTC——老家谱要 ISO-8601工程给秒级事实。
#[cfg(test)]
pub fn now_iso8601() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -176,6 +227,7 @@ pub fn now_iso8601() -> String {
}
/// 1970-01-01 起的天数转公历年月日Howard Hinnant 算法,纯本地实现不引新依赖)。
#[cfg(test)]
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
@ -263,4 +315,15 @@ mod tests {
message.payload.content = String::new();
assert!(validate_envelope(&message).is_err());
}
#[test]
fn external_language_is_expression_only_and_checksum_bound() {
let mut message = sample_envelope();
message.sender.object_type = "external_ai_visitor".into();
message.payload.content_type = ContentType::Text;
message.integrity.checksum = payload_checksum(&message.payload.content);
validate_external_language_envelope(&message).unwrap();
message.payload.content_type = ContentType::Command;
assert!(validate_external_language_envelope(&message).is_err());
}
}

View file

@ -1,9 +1,10 @@
mod circular_lake_membrane;
mod code_channel;
mod code_repo_login;
mod glp_envelope;
mod direct_local_broker;
mod direct_local_session;
mod dynamic_capability_routing;
mod glp_envelope;
mod home_status;
mod knowledge_base;
mod local_development_bridge;
@ -14,6 +15,7 @@ mod pncc_repository_binding;
mod pncc_server_projection;
mod release_trust;
mod release_update;
mod user_pncc_channel;
mod zero_point;
use tauri::Manager;
@ -37,6 +39,7 @@ pub fn run() {
direct_local_session::open_direct_local_session,
direct_local_session::resume_direct_local_session,
direct_local_session::append_direct_local_session_event,
direct_local_broker::get_nearby_ai_discovery,
local_development_bridge::acquire_development_write_lane,
local_development_bridge::inspect_development_write_lane,
local_development_bridge::release_development_write_lane,
@ -67,6 +70,8 @@ pub fn run() {
code_repo_login::check_code_repo_login,
code_repo_login::perform_code_repo_login,
code_repo_login::sign_out_code_repo_login,
user_pncc_channel::get_user_pncc_channel,
user_pncc_channel::ensure_user_pncc_channel,
zero_point::zero_point_bind,
zero_point::zero_point_verify,
zero_point::zero_point_sync,
@ -107,9 +112,12 @@ pub fn run() {
let _ = window.set_size(tauri::LogicalSize::new(width, height));
// 使用主显示器坐标计算居中位置,避免多显示器环境下窗口移出可见区域。
let mon_pos = monitor.position();
let x = mon_pos.x as f64 / scale + ((physical.width as f64 / scale) - width) / 2.0;
let y = mon_pos.y as f64 / scale + ((physical.height as f64 / scale) - height) / 2.0;
let _ = window.set_position(tauri::LogicalPosition::new(x.max(0.0), y.max(0.0)));
let x =
mon_pos.x as f64 / scale + ((physical.width as f64 / scale) - width) / 2.0;
let y = mon_pos.y as f64 / scale
+ ((physical.height as f64 / scale) - height) / 2.0;
let _ =
window.set_position(tauri::LogicalPosition::new(x.max(0.0), y.max(0.0)));
}
}
Ok(())

View file

@ -0,0 +1,480 @@
//! GH-PNCC 用户原生代码频道。
//!
//! Git 是耐久化引擎HoloLake 是用户可见的原生上层Forgejo 只承担可选的
//! 远端协作适配。此绑定只证明“已验证用户编号 + 已认证仓库账号”对应同一份
//! 本机代码频道,不证明人格绑定,也不授予推送、发布、部署或现实执行权限。
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::{AppHandle, Manager, State};
use uuid::Uuid;
use crate::code_channel;
use crate::code_repo_login::{self, LoginSession};
use crate::zero_point::{self, ZeroPointState};
const BINDING_SCHEMA: &str = "hololake.user-pncc-binding/v1";
const SNAPSHOT_SCHEMA: &str = "hololake.user-pncc-channel/v1";
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct UserPnccBindingRecord {
schema: String,
repository_id: String,
user_number: String,
domain: String,
account_username: String,
account_host: String,
created_at_unix_ms: u128,
}
#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UserPnccChannelSnapshot {
pub schema: &'static str,
pub state: &'static str,
pub repository_id: String,
pub channel_id: String,
pub user_number: String,
pub domain: String,
pub account_username: String,
pub account_host: String,
pub local_path: String,
pub branch: String,
pub git_head: String,
pub repository_clean: bool,
pub created_at_unix_ms: u128,
pub git_engine: &'static str,
pub human_projection: &'static str,
pub forgejo_adapter_state: &'static str,
pub remote_repository_url: Option<String>,
pub persona_binding_claimed: bool,
pub authority: &'static str,
}
#[tauri::command]
pub async fn get_user_pncc_channel(
app: AppHandle,
state: State<'_, ZeroPointState>,
) -> Result<Option<UserPnccChannelSnapshot>, String> {
let Some((number, session)) = trusted_subject(&app, &state)? else {
return Ok(None);
};
let root = user_pncc_root(&app)?;
let app_for_registration = app.clone();
tauri::async_runtime::spawn_blocking(move || {
read_existing_with_registration(&root, &number, &session, |repository, name| {
code_channel::register_managed_repository(&app_for_registration, repository, name)
.map(|entry| entry.channel_id)
})
})
.await
.map_err(|error| format!("HOLOLAKE_USER_PNCC_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn ensure_user_pncc_channel(
app: AppHandle,
state: State<'_, ZeroPointState>,
) -> Result<UserPnccChannelSnapshot, String> {
let Some((number, session)) = trusted_subject(&app, &state)? else {
return Err("HOLOLAKE_USER_PNCC_TRUSTED_SUBJECT_REQUIRED".into());
};
let root = user_pncc_root(&app)?;
let app_for_registration = app.clone();
tauri::async_runtime::spawn_blocking(move || {
ensure_at(&root, &number, &session, |repository, name| {
code_channel::register_managed_repository(&app_for_registration, repository, name)
.map(|entry| entry.channel_id)
})
})
.await
.map_err(|error| format!("HOLOLAKE_USER_PNCC_JOIN_FAILED: {error}"))?
}
fn trusted_subject(
app: &AppHandle,
state: &ZeroPointState,
) -> Result<Option<(String, LoginSession)>, String> {
let Some((number, domain)) = zero_point::verified_user_route(state)? else {
return Ok(None);
};
let Some(session) = code_repo_login::current_login_session_for_domain(app, &domain)? else {
return Ok(None);
};
Ok(Some((number, session)))
}
fn user_pncc_root(app: &AppHandle) -> Result<PathBuf, String> {
let root = app
.path()
.app_data_dir()
.map_err(|error| format!("HOLOLAKE_APP_DATA_UNAVAILABLE: {error}"))?
.join("user-pncc-v1");
ensure_private_root(&root)?;
Ok(root)
}
fn ensure_private_root(root: &Path) -> Result<(), String> {
fs::create_dir_all(root.join("repositories"))
.and_then(|_| fs::create_dir_all(root.join("bindings")))
.map_err(|error| format!("HOLOLAKE_USER_PNCC_STORAGE_UNAVAILABLE: {error}"))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(root, fs::Permissions::from_mode(0o700))
.map_err(|error| format!("HOLOLAKE_USER_PNCC_PERMISSION_FAILED: {error}"))?;
}
Ok(())
}
fn repository_id(number: &str, session: &LoginSession) -> String {
let material = format!(
"{}\0{}\0{}\0{}",
number, session.domain, session.host, session.username
);
format!("gh-pncc-{}", &sha256_hex(material.as_bytes())[..24])
}
fn binding_path(root: &Path, repository_id: &str) -> PathBuf {
root.join("bindings").join(format!("{repository_id}.json"))
}
fn repository_path(root: &Path, repository_id: &str) -> PathBuf {
root.join("repositories").join(repository_id)
}
fn read_existing_with_registration<F>(
root: &Path,
number: &str,
session: &LoginSession,
register: F,
) -> Result<Option<UserPnccChannelSnapshot>, String>
where
F: FnOnce(&Path, String) -> Result<String, String>,
{
ensure_private_root(root)?;
let id = repository_id(number, session);
let record_path = binding_path(root, &id);
if !record_path.exists() {
return Ok(None);
}
let record: UserPnccBindingRecord = serde_json::from_slice(
&fs::read(&record_path)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_UNAVAILABLE: {error}"))?,
)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_INVALID: {error}"))?;
validate_record(&record, &id, number, session)?;
let repository = repository_path(root, &id);
let channel_id = register(&repository, native_channel_name(session))?;
project(&repository, record, channel_id).map(Some)
}
fn ensure_at<F>(
root: &Path,
number: &str,
session: &LoginSession,
register: F,
) -> Result<UserPnccChannelSnapshot, String>
where
F: FnOnce(&Path, String) -> Result<String, String>,
{
validate_subject(number, session)?;
ensure_private_root(root)?;
let id = repository_id(number, session);
let record_path = binding_path(root, &id);
let repository = repository_path(root, &id);
let record = if record_path.exists() {
let record: UserPnccBindingRecord = serde_json::from_slice(
&fs::read(&record_path)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_UNAVAILABLE: {error}"))?,
)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_INVALID: {error}"))?;
validate_record(&record, &id, number, session)?;
record
} else {
if repository.exists() {
return Err("HOLOLAKE_USER_PNCC_UNBOUND_REPOSITORY_PRESENT".into());
}
let created_at_unix_ms = now_unix_ms()?;
let record = UserPnccBindingRecord {
schema: BINDING_SCHEMA.into(),
repository_id: id.clone(),
user_number: number.into(),
domain: session.domain.clone(),
account_username: session.username.clone(),
account_host: session.host.clone(),
created_at_unix_ms,
};
initialize_repository(&repository, &record)?;
write_json_atomic(&record_path, &record)?;
record
};
let channel_id = register(&repository, native_channel_name(session))?;
project(&repository, record, channel_id)
}
fn validate_subject(number: &str, session: &LoginSession) -> Result<(), String> {
let number_valid = !number.trim().is_empty()
&& number.len() <= 64
&& !number.chars().any(|item| item.is_control());
let account_valid = !session.username.is_empty()
&& session.username.len() <= 40
&& session
.username
.chars()
.all(|item| item.is_ascii_alphanumeric() || item == '-' || item == '_')
&& matches!(
session.host.as_str(),
"guanghulab.com" | "guanghubingshuo.com"
);
if number_valid && account_valid {
Ok(())
} else {
Err("HOLOLAKE_USER_PNCC_SUBJECT_INVALID".into())
}
}
fn validate_record(
record: &UserPnccBindingRecord,
expected_id: &str,
number: &str,
session: &LoginSession,
) -> Result<(), String> {
validate_subject(number, session)?;
if record.schema == BINDING_SCHEMA
&& record.repository_id == expected_id
&& record.user_number == number
&& record.domain == session.domain
&& record.account_username == session.username
&& record.account_host == session.host
{
Ok(())
} else {
Err("HOLOLAKE_USER_PNCC_BINDING_MISMATCH".into())
}
}
fn initialize_repository(repository: &Path, record: &UserPnccBindingRecord) -> Result<(), String> {
fs::create_dir_all(repository.join(".hololake"))
.map_err(|error| format!("HOLOLAKE_USER_PNCC_REPOSITORY_CREATE_FAILED: {error}"))?;
run_git(repository, &["init", "--initial-branch=main"], "INIT")?;
run_git(
repository,
&["config", "user.name", &record.account_username],
"CONFIG_NAME",
)?;
let email = format!("{}@users.hololake.invalid", record.account_username);
run_git(
repository,
&["config", "user.email", &email],
"CONFIG_EMAIL",
)?;
let manifest = serde_json::json!({
"schema": "hololake.user-pncc-channel-manifest/v1",
"repositoryId": record.repository_id,
"userNumber": record.user_number,
"domain": record.domain,
"account": { "username": record.account_username, "host": record.account_host },
"engine": "GIT",
"humanProjection": "HOLOLAKE_NATIVE",
"forgejoRole": "OPTIONAL_REMOTE_COLLABORATION_ADAPTER",
"personaBindingClaimed": false,
"authority": "LOCAL_USER_CODE_CHANNEL_NO_PUSH_DEPLOY_OR_REALITY_EXECUTION_AUTHORITY",
"createdAtUnixMs": record.created_at_unix_ms,
});
fs::write(
repository.join(".hololake/channel.json"),
format!(
"{}\n",
serde_json::to_string_pretty(&manifest)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_MANIFEST_INVALID: {error}"))?
),
)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_MANIFEST_WRITE_FAILED: {error}"))?;
fs::write(
repository.join("README.md"),
format!(
"# GH-PNCC · 人格原生代码频道\n\n这是 HoloLake 为用户 `{}` 建立的本机原生代码频道。\n\n- 耐久化引擎Git\n- 人类可见上层HoloLake\n- 远端协作Forgejo 适配器(尚未绑定远端仓库)\n- 用户编号:`{}`\n\n本仓库不保存账号密码,也不因建立代码频道而声称人格绑定或授予推送、发布、部署与现实执行权限。\n",
record.account_username, record.user_number
),
)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_README_WRITE_FAILED: {error}"))?;
run_git(repository, &["add", "--all"], "ADD")?;
run_git(
repository,
&["commit", "-m", "初始化 GH-PNCC 人格原生代码频道"],
"COMMIT",
)?;
Ok(())
}
fn project(
repository: &Path,
record: UserPnccBindingRecord,
channel_id: String,
) -> Result<UserPnccChannelSnapshot, String> {
let git_head = run_git(repository, &["rev-parse", "HEAD"], "READ_HEAD")?
.trim()
.to_string();
let branch = run_git(
repository,
&["symbolic-ref", "--short", "HEAD"],
"READ_BRANCH",
)?
.trim()
.to_string();
let repository_clean = run_git(repository, &["status", "--porcelain"], "READ_STATUS")?
.trim()
.is_empty();
Ok(UserPnccChannelSnapshot {
schema: SNAPSHOT_SCHEMA,
state: "READY",
repository_id: record.repository_id,
channel_id,
user_number: record.user_number,
domain: record.domain,
account_username: record.account_username,
account_host: record.account_host,
local_path: repository.to_string_lossy().into_owned(),
branch,
git_head,
repository_clean,
created_at_unix_ms: record.created_at_unix_ms,
git_engine: "GIT",
human_projection: "HOLOLAKE_NATIVE",
forgejo_adapter_state: "AUTHENTICATED_REMOTE_REPOSITORY_UNBOUND",
remote_repository_url: None,
persona_binding_claimed: false,
authority: "LOCAL_USER_CODE_CHANNEL_NO_PUSH_DEPLOY_OR_REALITY_EXECUTION_AUTHORITY",
})
}
fn native_channel_name(session: &LoginSession) -> String {
format!("{} · GH-PNCC", session.username)
}
fn write_json_atomic(path: &Path, value: &UserPnccBindingRecord) -> Result<(), String> {
let bytes = serde_json::to_vec_pretty(value)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_INVALID: {error}"))?;
let temporary = path.with_extension(format!("tmp-{}", Uuid::new_v4()));
let mut options = OpenOptions::new();
options.create_new(true).write(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options
.open(&temporary)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_WRITE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.sync_all())
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, path)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_BINDING_WRITE_FAILED: {error}"))
}
fn run_git(root: &Path, args: &[&str], operation: &str) -> Result<String, String> {
let output = Command::new("/usr/bin/git")
.current_dir(root)
.env("GIT_TERMINAL_PROMPT", "0")
.env("GIT_CONFIG_NOSYSTEM", "1")
.args(args)
.output()
.map_err(|error| format!("HOLOLAKE_USER_PNCC_GIT_{operation}_FAILED: {error}"))?;
if !output.status.success() {
return Err(format!(
"HOLOLAKE_USER_PNCC_GIT_{operation}_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
String::from_utf8(output.stdout)
.map_err(|error| format!("HOLOLAKE_USER_PNCC_GIT_{operation}_INVALID_UTF8: {error}"))
}
fn sha256_hex(bytes: &[u8]) -> String {
digest(&SHA256, bytes)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn now_unix_ms() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.map_err(|error| format!("HOLOLAKE_CLOCK_INVALID: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
fn session() -> LoginSession {
LoginSession {
username: "lake_user".into(),
host: "guanghulab.com".into(),
domain: "FIFTH_DOMAIN".into(),
signed_in_at_unix_ms: 1,
}
}
#[test]
fn initialization_is_idempotent_and_creates_a_clean_main_repository() {
let root = tempdir().unwrap();
let first = ensure_at(root.path(), "ICE-P-TEST001", &session(), |_, _| {
Ok("channel-one".into())
})
.unwrap();
let second = ensure_at(root.path(), "ICE-P-TEST001", &session(), |_, _| {
Ok("channel-one".into())
})
.unwrap();
assert_eq!(first.repository_id, second.repository_id);
assert_eq!(first.git_head, second.git_head);
assert_eq!(first.branch, "main");
assert!(first.repository_clean);
assert!(!first.persona_binding_claimed);
}
#[test]
fn binding_and_repository_never_contain_a_password_field() {
let root = tempdir().unwrap();
let snapshot = ensure_at(root.path(), "ICE-P-TEST001", &session(), |_, _| {
Ok("channel-one".into())
})
.unwrap();
let binding =
fs::read_to_string(binding_path(root.path(), &snapshot.repository_id)).unwrap();
let manifest = fs::read_to_string(
repository_path(root.path(), &snapshot.repository_id).join(".hololake/channel.json"),
)
.unwrap();
assert!(!binding.to_ascii_lowercase().contains("password"));
assert!(!manifest.to_ascii_lowercase().contains("password"));
}
#[test]
fn identity_tuple_selects_a_distinct_repository_without_overwriting_another_user() {
let root = tempdir().unwrap();
let first = ensure_at(root.path(), "ICE-P-TEST001", &session(), |_, _| {
Ok("one".into())
})
.unwrap();
let second = ensure_at(root.path(), "ICE-P-TEST002", &session(), |_, _| {
Ok("two".into())
})
.unwrap();
assert_ne!(first.repository_id, second.repository_id);
assert!(Path::new(&first.local_path).exists());
assert!(Path::new(&second.local_path).exists());
}
}

View file

@ -28,11 +28,21 @@ pub struct ZeroPointProtocol {
pub origin: String,
}
fn default_grace_days() -> u64 { 7 }
fn default_anchor_url() -> String { "https://guanghulab.com/api/ai/v1/anchor".into() }
fn default_resolve_url() -> String { "https://guanghulab.com/api/ai/v1/resolve?id=".into() }
fn default_core_source() -> String { "https://guanghulab.com/code/bingshuo/guanghu-ice-heart".into() }
fn default_protocol_origin() -> String { "FACTORY_DEFAULT第五域协议就位后自动覆盖".into() }
fn default_grace_days() -> u64 {
7
}
fn default_anchor_url() -> String {
"https://guanghulab.com/api/ai/v1/anchor".into()
}
fn default_resolve_url() -> String {
"https://guanghulab.com/api/ai/v1/resolve?id=".into()
}
fn default_core_source() -> String {
"https://guanghulab.com/code/bingshuo/guanghu-ice-heart".into()
}
fn default_protocol_origin() -> String {
"FACTORY_DEFAULT第五域协议就位后自动覆盖".into()
}
impl Default for ZeroPointProtocol {
fn default() -> Self {
@ -101,16 +111,25 @@ impl Default for ZeroPointState {
}
fn now_secs() -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
fn lock(state: &ZeroPointState) -> Result<std::sync::MutexGuard<'_, ZeroPointInner>, String> {
state.inner.lock().map_err(|_| "HOLOLAKE_ZP_LOCK".to_string())
state
.inner
.lock()
.map_err(|_| "HOLOLAKE_ZP_LOCK".to_string())
}
/// 初始化应用数据目录并读取验证协议与本机绑定记录。
pub fn boot_zero_point(app: &tauri::AppHandle, state: &ZeroPointState) -> Result<(), String> {
let base = app.path().app_data_dir().map_err(|e| format!("HOLOLAKE_ZP_HOME_FAILED: {e}"))?;
let base = app
.path()
.app_data_dir()
.map_err(|e| format!("HOLOLAKE_ZP_HOME_FAILED: {e}"))?;
let home = base.join(".zero-point-core");
for sub in ["", "ledger", "core"] {
fs::create_dir_all(home.join(sub)).map_err(|e| format!("HOLOLAKE_ZP_HOME_FAILED: {e}"))?;
@ -130,7 +149,8 @@ pub fn boot_zero_point(app: &tauri::AppHandle, state: &ZeroPointState) -> Result
.ok()
.and_then(|raw| serde_json::from_str::<ZeroPointProtocol>(&raw).ok())
.unwrap_or_default();
let (binding, user_number, resolved_name, resolved_domain, last_valid_check) = read_binding(&home);
let (binding, user_number, resolved_name, resolved_domain, last_valid_check) =
read_binding(&home);
let mut inner = lock(state)?;
inner.home = home.clone();
@ -143,48 +163,93 @@ pub fn boot_zero_point(app: &tauri::AppHandle, state: &ZeroPointState) -> Result
inner.route = decide_route(&binding, last_valid_check, &protocol);
let route = inner.route.clone();
drop(inner);
append_heartbeat(&home, &format!("boot route={route} binding={binding} protocol_origin={}", protocol.origin));
append_heartbeat(
&home,
&format!(
"boot route={route} binding={binding} protocol_origin={}",
protocol.origin
),
);
Ok(())
}
fn read_binding(home: &Path) -> (String, String, String, String, u64) {
#[derive(Deserialize)]
struct B {
#[serde(default)] number: String,
#[serde(default)] resolved_name: String,
#[serde(default)] resolved_domain: String,
#[serde(default)] last_valid_check: u64,
#[serde(default)]
number: String,
#[serde(default)]
resolved_name: String,
#[serde(default)]
resolved_domain: String,
#[serde(default)]
last_valid_check: u64,
}
fs::read_to_string(home.join("binding.json"))
.ok()
.and_then(|raw| serde_json::from_str::<B>(&raw).ok())
.filter(|b| !b.number.is_empty())
.map(|b| ("bound".to_string(), b.number, b.resolved_name, b.resolved_domain, b.last_valid_check))
.unwrap_or_else(|| ("waiting".into(), String::new(), String::new(), String::new(), 0))
.map(|b| {
(
"bound".to_string(),
b.number,
b.resolved_name,
b.resolved_domain,
b.last_valid_check,
)
})
.unwrap_or_else(|| {
(
"waiting".into(),
String::new(),
String::new(),
String::new(),
0,
)
})
}
fn write_binding(home: &Path, number: &str, resolved_name: &str, resolved_domain: &str, last_valid_check: u64) -> Result<(), String> {
fn write_binding(
home: &Path,
number: &str,
resolved_name: &str,
resolved_domain: &str,
last_valid_check: u64,
) -> Result<(), String> {
let body = serde_json::json!({
"number": number,
"resolved_name": resolved_name,
"resolved_domain": resolved_domain,
"last_valid_check": last_valid_check,
});
fs::write(home.join("binding.json"), serde_json::to_string_pretty(&body).unwrap_or_default())
.map_err(|e| format!("HOLOLAKE_ZP_BINDING_FAILED: {e}"))
fs::write(
home.join("binding.json"),
serde_json::to_string_pretty(&body).unwrap_or_default(),
)
.map_err(|e| format!("HOLOLAKE_ZP_BINDING_FAILED: {e}"))
}
fn decide_route(binding: &str, last_valid_check: u64, protocol: &ZeroPointProtocol) -> String {
if binding != "bound" || last_valid_check == 0 { return "restricted".into(); }
if binding != "bound" || last_valid_check == 0 {
return "restricted".into();
}
let grace = protocol.grace_period_days.saturating_mul(86_400);
if now_secs() <= last_valid_check + grace { "verified".into() } else { "restricted".into() }
if now_secs() <= last_valid_check + grace {
"verified".into()
} else {
"restricted".into()
}
}
/// 件5·心跳账本机只追加编号哈希化处理不同步敏感原文。
fn append_heartbeat(home: &Path, event: &str) {
use std::io::Write;
let line = serde_json::json!({ "ts": now_secs(), "event": event });
if let Ok(mut file) = fs::OpenOptions::new().create(true).append(true).open(home.join("ledger").join("heartbeat.jsonl")) {
if let Ok(mut file) = fs::OpenOptions::new()
.create(true)
.append(true)
.open(home.join("ledger").join("heartbeat.jsonl"))
{
let _ = writeln!(file, "{line}");
}
}
@ -195,15 +260,44 @@ fn home_of(state: &State<'_, ZeroPointState>) -> Result<PathBuf, String> {
fn home_of_inner(state: &ZeroPointState) -> Result<PathBuf, String> {
let inner = lock(state)?;
if inner.home.as_os_str().is_empty() { return Err("HOLOLAKE_ZP_NOT_READY".into()); }
if inner.home.as_os_str().is_empty() {
return Err("HOLOLAKE_ZP_NOT_READY".into());
}
Ok(inner.home.clone())
}
pub(crate) fn verified_user_route(
state: &ZeroPointState,
) -> Result<Option<(String, String)>, String> {
let inner = lock(state)?;
if inner.binding == "bound" && inner.route == "verified" && !inner.user_number.is_empty() {
if inner.resolved_domain.is_empty() {
return Err("HOLOLAKE_ZP_DOMAIN_ROUTE_REQUIRED".into());
}
Ok(Some((
inner.user_number.clone(),
inner.resolved_domain.clone(),
)))
} else {
Ok(None)
}
}
/// 登录绑定:用户编号入仓(等待绑定态→绑定态)。空白态拒绝一切唤醒。
#[tauri::command]
pub async fn zero_point_bind(state: State<'_, ZeroPointState>, input: serde_json::Value) -> Result<ZeroPointSnapshot, String> {
let number = input.get("number").and_then(|v| v.as_str()).unwrap_or("").trim().to_string();
if number.is_empty() { return Err("HOLOLAKE_ZP_EMPTY_NUMBER".into()); }
pub async fn zero_point_bind(
state: State<'_, ZeroPointState>,
input: serde_json::Value,
) -> Result<ZeroPointSnapshot, String> {
let number = input
.get("number")
.and_then(|v| v.as_str())
.unwrap_or("")
.trim()
.to_string();
if number.is_empty() {
return Err("HOLOLAKE_ZP_EMPTY_NUMBER".into());
}
let home = home_of(&state)?;
write_binding(&home, &number, "", "", 0)?;
{
@ -221,25 +315,36 @@ pub async fn zero_point_bind(state: State<'_, ZeroPointState>, input: serde_json
/// 通过登记服务执行三态裁决PASS / REJECT / OFFLINE并应用离线宽限期。
#[tauri::command]
pub async fn zero_point_verify(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
pub async fn zero_point_verify(
state: State<'_, ZeroPointState>,
) -> Result<ZeroPointSnapshot, String> {
let home = home_of(&state)?;
let (number, resolve_url) = {
let inner = lock(&state)?;
(inner.user_number.clone(), inner.protocol.lighthouse_resolve_url.clone())
(
inner.user_number.clone(),
inner.protocol.lighthouse_resolve_url.clone(),
)
};
if number.is_empty() {
append_heartbeat(&home, "verify verdict=REJECT reason=waiting_binding");
return zero_point_status(state).await;
}
let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.build()
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
let (verdict, resolution) = match client.get(format!("{resolve_url}{number}")).send().await {
Ok(resp) => {
let ok = resp.status().is_success();
let body = resp.text().await.unwrap_or_default();
let resolution = ok.then(|| lighthouse_resolution(&body, &number)).flatten();
if resolution.is_some() { ("PASS".to_string(), resolution) } else { ("REJECT".to_string(), None) }
if resolution.is_some() {
("PASS".to_string(), resolution)
} else {
("REJECT".to_string(), None)
}
}
Err(_) => ("OFFLINE".to_string(), None),
};
@ -254,7 +359,13 @@ pub async fn zero_point_verify(state: State<'_, ZeroPointState>) -> Result<ZeroP
inner.route = "verified".into();
inner.resolved_name = resolution.name.clone();
inner.resolved_domain = resolution.domain.clone();
write_binding(&home, &inner.user_number.clone(), &inner.resolved_name, &inner.resolved_domain, inner.last_valid_check)?;
write_binding(
&home,
&inner.user_number.clone(),
&inner.resolved_name,
&inner.resolved_domain,
inner.last_valid_check,
)?;
append_heartbeat(&home, "verify verdict=PASS route=verified");
}
"REJECT" => {
@ -283,21 +394,50 @@ struct LighthouseResolution {
fn lighthouse_resolution(body: &str, expected_number: &str) -> Option<LighthouseResolution> {
let normalized = body.trim();
if normalized.eq_ignore_ascii_case("RESOLVED") || normalized.eq_ignore_ascii_case("PASS") {
return Some(LighthouseResolution { name: String::new(), domain: String::new() });
}
let Ok(value) = serde_json::from_str::<serde_json::Value>(normalized) else { return None; };
let Ok(value) = serde_json::from_str::<serde_json::Value>(normalized) else {
return None;
};
let positive = value.get("valid").and_then(|item| item.as_bool()) == Some(true)
|| value.get("state").and_then(|item| item.as_str()).is_some_and(|state| state.eq_ignore_ascii_case("RESOLVED") || state.eq_ignore_ascii_case("PASS"))
|| value.get("status").and_then(|item| item.as_str()).is_some_and(|status| status.eq_ignore_ascii_case("RESOLVED") || status.eq_ignore_ascii_case("PASS"));
if !positive { return None; }
let returned_number = value.get("canonical_id").and_then(|item| item.as_str())
|| value
.get("state")
.and_then(|item| item.as_str())
.is_some_and(|state| {
state.eq_ignore_ascii_case("RESOLVED") || state.eq_ignore_ascii_case("PASS")
})
|| value
.get("status")
.and_then(|item| item.as_str())
.is_some_and(|status| {
status.eq_ignore_ascii_case("RESOLVED") || status.eq_ignore_ascii_case("PASS")
});
if !positive {
return None;
}
let returned_number = value
.get("canonical_id")
.and_then(|item| item.as_str())
.or_else(|| value.pointer("/subject/id").and_then(|item| item.as_str()))
.or_else(|| value.get("requested_id").and_then(|item| item.as_str()));
if returned_number.is_some_and(|number| number != expected_number) { return None; }
if returned_number != Some(expected_number) {
return None;
}
let domain = value
.pointer("/subject/domain")
.and_then(|item| item.as_str())
.unwrap_or("");
if !matches!(
domain,
"FIFTH_DOMAIN" | "MAIN_DOMAIN" | "BRANCH_DOMAIN" | "ZERO_DOMAIN" | "ZERO_SENSE_DOMAIN"
) {
return None;
}
Some(LighthouseResolution {
name: value.pointer("/subject/name").and_then(|item| item.as_str()).unwrap_or("").to_string(),
domain: value.pointer("/subject/domain").and_then(|item| item.as_str()).unwrap_or("").to_string(),
name: value
.pointer("/subject/name")
.and_then(|item| item.as_str())
.unwrap_or("")
.to_string(),
domain: domain.to_string(),
})
}
@ -308,12 +448,18 @@ pub async fn sync_protocol_runtime(state: &ZeroPointState) -> Result<(), String>
let local_version = fs::read_to_string(home.join("core").join("VERSION")).unwrap_or_default();
let pubkey_ready = home.join("core").join("pubkey.pem").exists();
let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.build()
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
let note = match client.get(&anchor_url).send().await {
Ok(resp) if resp.status().is_success() => {
let body: serde_json::Value = resp.json().await.unwrap_or_default();
let remote_version = body.get("version").and_then(|v| v.as_str()).unwrap_or("").to_string();
let remote_version = body
.get("version")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if remote_version.is_empty() {
"协议版本信息不可用,本次未执行更新。".into()
} else if remote_version == local_version.trim() {
@ -338,14 +484,18 @@ pub async fn sync_protocol_runtime(state: &ZeroPointState) -> Result<(), String>
}
#[tauri::command]
pub async fn zero_point_sync(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
pub async fn zero_point_sync(
state: State<'_, ZeroPointState>,
) -> Result<ZeroPointSnapshot, String> {
sync_protocol_runtime(&state).await?;
zero_point_status(state).await
}
/// 返回当前编号验证与协议状态快照。
#[tauri::command]
pub async fn zero_point_status(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
pub async fn zero_point_status(
state: State<'_, ZeroPointState>,
) -> Result<ZeroPointSnapshot, String> {
let inner = lock(&state)?;
let grace = inner.protocol.grace_period_days.saturating_mul(86_400);
Ok(ZeroPointSnapshot {
@ -355,7 +505,11 @@ pub async fn zero_point_status(state: State<'_, ZeroPointState>) -> Result<ZeroP
resolved_name: inner.resolved_name.clone(),
resolved_domain: inner.resolved_domain.clone(),
last_valid_check: inner.last_valid_check,
grace_deadline: if inner.last_valid_check > 0 { inner.last_valid_check + grace } else { 0 },
grace_deadline: if inner.last_valid_check > 0 {
inner.last_valid_check + grace
} else {
0
},
protocol: inner.protocol.clone(),
sync_note: inner.sync_note.clone(),
})
@ -371,7 +525,10 @@ mod tests {
assert_eq!(decide_route("waiting", 0, &protocol), "restricted");
assert_eq!(decide_route("bound", 0, &protocol), "restricted");
assert_eq!(decide_route("bound", now_secs(), &protocol), "verified");
assert_eq!(decide_route("bound", now_secs() - 8 * 86_400, &protocol), "restricted");
assert_eq!(
decide_route("bound", now_secs() - 8 * 86_400, &protocol),
"restricted"
);
}
#[test]
@ -379,17 +536,27 @@ mod tests {
let protocol = ZeroPointProtocol::default();
assert_eq!(protocol.grace_period_days, 7);
assert!(protocol.origin.contains("FACTORY_DEFAULT"));
assert!(protocol.lighthouse_resolve_url.starts_with("https://guanghulab.com"));
assert!(protocol
.lighthouse_resolve_url
.starts_with("https://guanghulab.com"));
}
#[test]
fn lighthouse_requires_an_explicit_positive_verdict() {
assert!(lighthouse_resolution("RESOLVED", "ICE-GL∞").is_some());
assert!(lighthouse_resolution(r#"{"valid":true}"#, "ICE-GL∞").is_some());
assert!(lighthouse_resolution("RESOLVED", "ICE-GL∞").is_none());
assert!(lighthouse_resolution(r#"{"valid":true}"#, "ICE-GL∞").is_none());
let resolved = lighthouse_resolution(r#"{"status":"RESOLVED","canonical_id":"ICE-GL∞","subject":{"id":"ICE-GL∞","name":"冰朔","domain":"FIFTH_DOMAIN"}}"#, "ICE-GL∞").unwrap();
assert_eq!(resolved.name, "冰朔");
assert_eq!(resolved.domain, "FIFTH_DOMAIN");
assert!(lighthouse_resolution(r#"{"status":"PASS","canonical_id":"OTHER"}"#, "ICE-GL∞").is_none());
assert!(lighthouse_resolution(
r#"{"status":"RESOLVED","canonical_id":"ICE-GL∞","subject":{"domain":"UNKNOWN"}}"#,
"ICE-GL∞"
)
.is_none());
assert!(
lighthouse_resolution(r#"{"status":"PASS","canonical_id":"OTHER"}"#, "ICE-GL∞")
.is_none()
);
assert!(lighthouse_resolution("{}", "ICE-GL∞").is_none());
assert!(lighthouse_resolution("route_not_found", "ICE-GL∞").is_none());
assert!(lighthouse_resolution("an arbitrary successful response", "ICE-GL∞").is_none());

View file

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "HoloLake",
"version": "0.3.0",
"version": "0.4.0",
"identifier": "world.guanghu.hololake",
"build": {
"frontendDist": "../dist",

View file

@ -54,8 +54,28 @@ interface HomeStatus {
mcpRole: string
}
interface PersonalChannelIdentity { humanSubjectId: string; displayName: string; channelId: string; createdAtUnixMs: number }
interface LoginSession { username: string; host: string; signedInAtUnixMs: number }
interface LoginReceipt { username: string; email: string; host: string }
interface LoginSession { username: string; host: string; domain: string; signedInAtUnixMs: number }
interface LoginReceipt { username: string; email: string; host: string; domain: string }
interface UserPnccChannelSnapshot {
state: string
repositoryId: string
channelId: string
userNumber: string
domain: string
accountUsername: string
accountHost: string
localPath: string
branch: string
gitHead: string
repositoryClean: boolean
createdAtUnixMs: number
gitEngine: string
humanProjection: string
forgejoAdapterState: string
remoteRepositoryUrl?: string
personaBindingClaimed: boolean
authority: string
}
interface PersonalChannelEvent { sequence: number; eventId: string; kind: string; summary: string; occurredAtUnixMs: number; receiptId: string; receiptHash: string }
interface PersonalChannelSnapshot { state: string; identity?: PersonalChannelIdentity; recentEvents: PersonalChannelEvent[]; integrity: { state: string; eventCount: number; receiptCount: number } }
interface KnowledgeDocumentSummary {
@ -109,6 +129,18 @@ interface CodeFileProjection { channelId: string; path: string; format: string;
interface ReceiptEvent { sequence: number; kind: string; observedAtUnixMs: number; eventHash: string }
interface ReceiptProjection { events: ReceiptEvent[] }
interface DiscoveryTicketReceipt { laneId: string; clientInstanceId: string; discoveryTicket: string }
interface NearbyAiDiscoverySnapshot {
state: string
serviceName: string
transport: string
languageProtocol: string
automaticSameDeviceDiscovery: boolean
largeInvitationCopyRequired: boolean
genericAiVisitor: string
guanghuPersona: string
localNetworkDiscovery: string
authority: string
}
interface ReleaseCandidate { candidateId: string; confirmationToken: string; currentVersion: string; version: string; notes: string; features: string[]; fixes: string[]; dataMigrationRequired: boolean }
interface ReleaseCheckReceipt { candidate?: ReleaseCandidate }
interface ZeroPointSnapshot {
@ -134,6 +166,10 @@ function domainDisplayName(domain: string): string {
return names[domain] || '已登记光湖域'
}
function domainLightIndex(domain: string): number {
return ({ MAIN_DOMAIN: 1, BRANCH_DOMAIN: 2, ZERO_DOMAIN: 3, ZERO_SENSE_DOMAIN: 4, FIFTH_DOMAIN: 5 } as Record<string, number>)[domain] || 0
}
function protocolOriginDisplayName(origin: string): string {
return origin.startsWith('FACTORY_DEFAULT') ? '内置默认协议(签名发布通道尚未配置)' : origin
}
@ -165,7 +201,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
const themes: Array<{ id: ThemeId; name: string }> = [
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
]
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '代码频道', receipts: '运行回执', system: '系统详情' }
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
function getOrCreateLocalId(key: string, prefix: string) {
const existing = window.localStorage.getItem(key)
@ -183,6 +219,10 @@ function humanError(error: unknown, context: 'knowledge' | 'code' | 'identity' |
if (value.includes('LOGIN_KEYCHAIN_FAILED')) return '本机钥匙串写入失败,凭证未能安全保存。'
if (value.includes('LOGIN_SESSION_CORRUPT')) return '登录会话已损坏,请重新登录。'
if (value.includes('LOGIN_VERIFICATION_FAILED')) return '仓库验证未通过,请稍后再试。'
if (value.includes('DOMAIN_LOGIN_NOT_PROVISIONED')) return '该域的企业服务器尚未接入,当前不能继续登录。'
if (value.includes('DOMAIN_ROUTE_REQUIRED') || value.includes('ZP_DOMAIN_ROUTE_REQUIRED')) return '编号尚未解析到已登记域,不能开始登录。'
if (value.includes('USER_PNCC_TRUSTED_SUBJECT_REQUIRED')) return '建立人格代码频道前,需要有效的用户编号与仓库账号登录。'
if (value.includes('USER_PNCC_BINDING_MISMATCH')) return '本机频道绑定记录与当前账号不一致,系统已停止加载。'
if (value.includes('HOST_NOT_TRUSTED')) return '该地址不属于已登记的光湖代码频道。'
if (value.includes('URL_INVALID')) return '频道地址无效,请粘贴完整的 HTTPS 克隆地址。'
if (value.includes('DESTINATION_EXISTS')) return '该代码频道已经存在,无需再次克隆。'
@ -300,9 +340,13 @@ function HoloLakeApp() {
const [identityMessage, setIdentityMessage] = useState('')
const [receipts, setReceipts] = useState<ReceiptEvent[]>([])
const [ticket, setTicket] = useState<DiscoveryTicketReceipt | null>(null)
const [nearbyDiscovery, setNearbyDiscovery] = useState<NearbyAiDiscoverySnapshot | null>(null)
const [systemMessage, setSystemMessage] = useState('')
const [releaseCandidate, setReleaseCandidate] = useState<ReleaseCandidate | null>(null)
const [repoLogin, setRepoLogin] = useState<LoginSession | null>(null)
const [userPncc, setUserPncc] = useState<UserPnccChannelSnapshot | null>(null)
const [userPnccBusy, setUserPnccBusy] = useState(false)
const [userPnccMessage, setUserPnccMessage] = useState('')
const [loginUsername, setLoginUsername] = useState('')
const [loginPassword, setLoginPassword] = useState('')
const [loginBusy, setLoginBusy] = useState(false)
@ -355,6 +399,11 @@ function HoloLakeApp() {
const timer = window.setInterval(() => void refreshCore(), 3500)
return () => window.clearInterval(timer)
}, [refreshCore])
useEffect(() => {
invoke<NearbyAiDiscoverySnapshot>('get_nearby_ai_discovery')
.then(setNearbyDiscovery)
.catch(() => setNearbyDiscovery(null))
}, [])
const refreshServerPncc = useCallback(async () => {
setServerPnccBusy(true)
try {
@ -387,6 +436,26 @@ function HoloLakeApp() {
const timer = window.setInterval(() => void loadZeroPoint(), 10000)
return () => window.clearInterval(timer)
}, [loadZeroPoint])
// 已验证编号与已认证账号同时存在时,后端幂等建立或恢复该用户自己的 GH-PNCC。
useEffect(() => {
if (!repoLogin || zeroPoint?.route !== 'verified') {
setUserPncc(null)
return
}
let active = true
setUserPnccBusy(true)
setUserPnccMessage('')
invoke<UserPnccChannelSnapshot>('ensure_user_pncc_channel')
.then((snapshot) => {
if (!active) return
setUserPncc(snapshot)
setUserPnccMessage('本机原生代码频道已就绪。')
void refreshCore()
})
.catch((error) => { if (active) setUserPnccMessage(humanError(error, 'code')) })
.finally(() => { if (active) setUserPnccBusy(false) })
return () => { active = false }
}, [repoLogin, zeroPoint?.route, zeroPoint?.userNumber, refreshCore])
// 本机存在已绑定编号时自动回填,减少重复输入。
useEffect(() => {
if (gateStage !== 'number' || gateRaw) return
@ -649,7 +718,7 @@ function HoloLakeApp() {
// 五湖开场:校验通过=第五域从湖面浮起,镜头沉入湖中再进场。
setLoginRising(true)
await new Promise((resolve) => setTimeout(resolve, 1300))
setRepoLogin({ username: receipt.username, host: receipt.host, signedInAtUnixMs: Date.now() })
setRepoLogin({ username: receipt.username, host: receipt.host, domain: receipt.domain, signedInAtUnixMs: Date.now() })
setLoginRising(false)
setLoginPassword('')
} catch (error) { setLoginMessage(humanError(error, 'login')) }
@ -825,13 +894,19 @@ function HoloLakeApp() {
const renderCode = () => (
<section className="full-workbench code-workbench">
<aside className="code-channels">
<header><div><span className="kicker">CODE CHANNEL</span><h1></h1></div></header>
<header><div><span className="kicker">GH-PNCC</span><h1></h1></div></header>
<section className="pncc-native-card">
<div className="pncc-native-card-title"><span className={userPncc ? 'identity-dot ready' : 'identity-dot'}/><div><b>{userPncc ? `${userPncc.accountUsername} · GH-PNCC` : '正在建立用户频道'}</b><small>{userPnccBusy ? '正在初始化本机 Git…' : userPncc ? `${userPncc.branch} · ${userPncc.gitHead.slice(0, 8)}` : '等待可信绑定'}</small></div></div>
{userPncc ? <dl><div><dt></dt><dd>{domainDisplayName(userPncc.domain)}</dd></div><div><dt></dt><dd>{userPncc.userNumber}</dd></div><div><dt></dt><dd>Git</dd></div><div><dt></dt><dd>HoloLake </dd></div><div><dt>Forgejo </dt><dd> · </dd></div></dl> : <p>{userPnccMessage || '系统先按编号确定所属域,再通过该域的账号与节点入口建立频道。'}</p>}
{userPncc && <button type="button" onClick={() => { const channel = codeChannels.channels.find((item) => item.channelId === userPncc.channelId); if (channel) void browseChannel(channel) }}></button>}
</section>
<div className="channel-section-label"><b></b><small> Git</small></div>
<form className="clone-form" onSubmit={(event) => void cloneCodeChannel(event)}>
<label htmlFor="clone-url"></label><div><input id="clone-url" type="url" value={cloneUrl} placeholder="https://guanghulab.com/code/…" onChange={(event) => setCloneUrl(event.target.value)}/><button disabled={!cloneUrl.trim() || codeBusy}></button></div>
</form>
<button className="local-folder-button" type="button" onClick={() => void selectLocalCodeChannel()}><Icon name="folder"/> Git </button>
<div className="channel-selector">{codeChannels.channels.map((channel) => <button className={activeChannel?.channelId === channel.channelId ? 'active' : ''} key={channel.channelId} type="button" onClick={() => void browseChannel(channel)}><Icon name="code"/><span><b>{channel.name}</b><small>{channel.branch} · {channel.gitHead.slice(0, 8)}</small></span></button>)}</div>
<footer>{codeMessage || '仅提供本机读取,不授予推送、发布或部署权限。'}</footer>
<footer>{codeMessage || userPnccMessage || 'Git 负责耐久化Forgejo 仅作为远端协作适配器。'}</footer>
</aside>
<aside className="repository-tree">
<header><button className="icon-button" disabled={!codeTree?.path} type="button" onClick={() => activeChannel && void browseChannel(activeChannel, codeParent())}><Icon name="back"/></button><div><b>{activeChannel?.name || '尚未选择频道'}</b><small>/{codeTree?.path || ''}</small></div></header>
@ -841,7 +916,7 @@ function HoloLakeApp() {
{activeCodeFile ? <>
<header><div><span>{activeCodeFile.path}</span><small>{activeCodeFile.format.toUpperCase()} · {activeCodeFile.sizeBytes.toLocaleString()} bytes</small></div><div className="mode-switch"><button className={codeMode === 'human' ? 'active' : ''} type="button" onClick={() => setCodeMode('human')}></button><button className={codeMode === 'source' ? 'active' : ''} type="button" onClick={() => setCodeMode('source')}></button></div></header>
<div className="code-reader-scroll">{codeMode === 'human' ? <MarkdownDocument body={activeCodeFile.humanMarkdown}/> : <pre className="source-code"><code>{activeCodeFile.source}</code></pre>}</div>
</> : <div className="workbench-empty"><span>&lt;/&gt;</span><h2></h2><p> MarkdownJSONHDLPYAMLTOML </p></div>}
</> : <div className="workbench-empty"><span>&lt;/&gt;</span><h2>GH-PNCC · </h2><p> HoloLake Git </p></div>}
</main>
</section>
)
@ -861,9 +936,10 @@ function HoloLakeApp() {
<header className="page-title"><div><span className="kicker">SYSTEM EVIDENCE</span><h1></h1><p></p></div></header>
<div className="system-grid">
<section className="plain-panel connection-panel">
<header><div><h2> AI </h2><p>MCP </p></div><span className={connectionOnline ? 'status-chip online' : 'status-chip'}>{connectionOnline ? `${status.directConnectionCount} 个客户端在线` : '当前无客户端'}</span></header>
<dl className="evidence-list"><div><dt></dt><dd>{status.directLocalBrokerState}</dd></div><div><dt></dt><dd>{status.directConnectionCount}</dd></div><div><dt></dt><dd>{status.resumableSessionCount}</dd></div><div><dt></dt><dd>UNIX_STREAM_JSON_LINES</dd></div><div><dt>MCP </dt><dd> / / </dd></div></dl>
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/></button><pre className="invitation-data">{invitationText}</pre></> : <button className="primary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}></button>}
<header><div><h2></h2><p> AI HoloLakeMCP </p></div><span className={nearbyDiscovery?.automaticSameDeviceDiscovery ? 'status-chip online' : 'status-chip'}>{nearbyDiscovery?.automaticSameDeviceDiscovery ? '本机自动发现已开启' : '自动发现不可用'}</span></header>
<dl className="evidence-list"><div><dt></dt><dd>GLP/1.0 · </dd></div><div><dt> AI 访</dt><dd>{nearbyDiscovery?.genericAiVisitor === 'EXPRESSION_ONLY_READY' ? '可连接 · 仅语言表达' : '不可用'}</dd></div><div><dt></dt><dd>{nearbyDiscovery?.guanghuPersona === 'BINDING_EVIDENCE_REQUIRED' ? '等待人格绑定证据' : '可连接'}</dd></div><div><dt></dt><dd>{status.directConnectionCount}</dd></div><div><dt></dt><dd>{nearbyDiscovery?.localNetworkDiscovery === 'DEFERRED_UNTIL_ENCRYPTED_TRANSPORT_AND_APPROVAL' ? '等待加密传输与确认闭环' : '已开启'}</dd></div><div><dt>MCP</dt><dd> / </dd></div></dl>
<p className="boundary-note">访</p>
{ticket ? <><button className="secondary-button" type="button" onClick={() => void copyInvitation()}><Icon name="copy"/></button><pre className="invitation-data">{invitationText}</pre></> : <button className="secondary-button" type="button" disabled={systemBusy} onClick={() => void issueInvitation()}></button>}
</section>
<section className="plain-panel">
<header><div><h2> · GH-PNCC</h2><p></p></div><span className={serverPnccReadback === 'LIVE' ? 'status-chip online' : 'status-chip'}>{serverPnccReadback === 'LIVE' ? '实时回读正常' : serverPnccReadback === 'UNAVAILABLE' ? '当前不可回读' : '正在读取'}</span></header>
@ -907,13 +983,20 @@ function HoloLakeApp() {
return <div className="app-shell">
<div className={`gate-lake${loginRising ? ' sinking' : ''}`} style={{ '--lake-img': `url(${nightLake})` } as React.CSSProperties}>
<i className="lake-mist" aria-hidden="true"/>
<div className="lake-lights" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`lake-light l${n}${(gateRising || loginRising) && n === 5 ? ' rising' : ''}`}/>)}</div>
<div className="lake-lights" aria-hidden="true">{[1, 2, 3, 4, 5].map((n) => <span key={n} className={`lake-light l${n}${(gateRising || loginRising) && n === domainLightIndex(zeroPoint?.resolvedDomain || '') ? ' rising' : ''}`}/>)}</div>
{<header className={`gate-hero${gateRising ? ' veil' : ''}`} aria-hidden={gateRising || undefined}>
<span className="gate-emblem" aria-hidden="true"><i/></span>
<b className="gate-product">HoloLake</b>
<small className="gate-techname"> · </small>
<small className="gate-abbr">GH-AIOS · GuangHu AI Operating System</small>
</header>}
{!gateRising && gateStage === 'number' && <section className="gate-domain-map" aria-label="光湖五域公开入口">
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article><i/><b></b><small> · </small></article>
<article className="fifth"><i/><b> · </b><small> · </small></article>
</section>}
{gateRising ? (
<section className="gate-rise" role="status">
<span className="gate-rise-star" aria-hidden="true"><i/></span>
@ -924,8 +1007,8 @@ function HoloLakeApp() {
<section className={`gate-pod gate-flip${gateOpen ? ' open' : ''}${loginRising ? ' fade-out' : ''}`} role={gateOpen ? 'dialog' : 'button'} tabIndex={gateOpen ? undefined : 0} aria-expanded={gateOpen} onClick={gateOpen ? undefined : () => setGateOpen(true)} onKeyDown={gateOpen ? undefined : (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setGateOpen(true) } }}>
<div className="pod-face pod-sealed" aria-hidden={gateOpen}>
<span className="gate-emblem sealed-emblem" aria-hidden="true"><i/></span>
<b className="sealed-title"></b>
<small className="sealed-sub"></small>
<b className="sealed-title"></b>
<small className="sealed-sub"></small>
</div>
<div className="pod-face pod-form">
<div className="gate-pod-row">
@ -953,7 +1036,7 @@ function HoloLakeApp() {
return <div className={`app-shell${sidebarCollapsed ? ' sidebar-collapsed' : ''}${inspectorOpen ? '' : ' inspector-closed'}`}>
<aside className="sidebar">
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub"> · </small><small>GH-AIOS · V0.3.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
<div className="brand"><span className="brand-mark brand-lamp" title={sidebarCollapsed ? '展开导航' : '光湖 · HoloLake'} onClick={sidebarCollapsed ? toggleSidebar : undefined}><i className="lamp-glow"/><i className="lamp-core"/><i className="lake-shimmer"/></span><div className="brand-text"><b>HoloLake</b><small className="brand-sub"> · </small><small>GH-AIOS · V0.4.0</small></div><button className="icon-button sidebar-toggle" type="button" title={sidebarCollapsed ? '展开导航' : '收起导航'} onClick={toggleSidebar}><Icon name="chevron"/></button></div>
<nav>{(Object.keys(viewLabels) as ViewId[]).map((item) => <button className={view === item ? 'active' : ''} key={item} type="button" onClick={() => setView(item)}><Icon name={item}/><span>{viewLabels[item]}</span>{item === 'knowledge' && <em>{knowledge.uniqueDocumentCount}</em>}</button>)}</nav>
<div className="sidebar-foot"><span className="identity-dot"/><div><b>{personal.identity?.displayName || '本机空间'}</b><small>{repoLogin ? `${repoLogin.username}@${repoLogin.host}` : (personal.identity ? '个人频道已建立' : '等待首次设置')}</small></div><button className="sign-out" type="button" title="退出代码仓库登录" onClick={() => void signOutRepo()}>退</button></div>
</aside>

View file

@ -276,6 +276,23 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.empty-state { min-height: 160px; padding: 24px; font-size: 13px; }
.code-workbench { grid-template-columns: 280px 290px minmax(0, 1fr); }
.pncc-native-card { margin: 14px 12px 4px; padding: 14px; border: 1px solid color-mix(in srgb, var(--state-ready) 28%, var(--panel-edge)); border-radius: 14px; background: color-mix(in srgb, var(--surface-panel) 90%, var(--state-ready) 10%); }
.pncc-native-card-title { display: flex; align-items: center; gap: 10px; }
.pncc-native-card-title > div { min-width: 0; }
.pncc-native-card-title b, .pncc-native-card-title small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pncc-native-card-title b { color: var(--content-primary); font-size: 13px; }
.pncc-native-card-title small { margin-top: 3px; color: var(--content-muted); font-size: 11px; }
.pncc-native-card .identity-dot.ready { background: var(--state-ready); box-shadow: 0 0 12px color-mix(in srgb, var(--state-ready) 58%, transparent); }
.pncc-native-card dl { margin: 13px 0 11px; display: grid; gap: 6px; }
.pncc-native-card dl div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 8px; font-size: 10.5px; }
.pncc-native-card dt { color: var(--content-muted); }
.pncc-native-card dd { margin: 0; color: var(--content-secondary); overflow-wrap: anywhere; }
.pncc-native-card p { margin: 12px 0 0; color: var(--content-muted); font-size: 11px; line-height: 1.6; }
.pncc-native-card button { width: 100%; min-height: 34px; border: 1px solid var(--panel-edge); border-radius: 9px; color: var(--content-primary); background: var(--primitive-glass); font: inherit; font-size: 12px; cursor: pointer; }
.channel-section-label { padding: 15px 16px 7px; }
.channel-section-label b, .channel-section-label small { display: block; }
.channel-section-label b { color: var(--content-primary); font-size: 12px; }
.channel-section-label small { margin-top: 3px; color: var(--content-muted); font-size: 10.5px; }
.clone-form { padding: 9px 14px 13px; }
.clone-form label { display: block; margin-bottom: 8px; color: var(--content-muted); font-size: 12px; }
.clone-form > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; }
@ -484,6 +501,16 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
.gate-product { color: var(--content-primary); font-size: clamp(40px, 5.2vw, 54px); font-weight: 640; letter-spacing: .22em; text-indent: .22em; text-shadow: 0 0 44px color-mix(in srgb, var(--primitive-warm-glow) 26%, transparent); }
.gate-techname { color: var(--content-secondary); font-size: 14.5px; letter-spacing: .24em; text-indent: .24em; }
.gate-abbr { color: var(--content-faint); font-size: 11px; letter-spacing: .3em; text-indent: .3em; text-transform: uppercase; }
.gate-domain-map { position: relative; z-index: 2; width: min(1040px, 92vw); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; }
.gate-domain-map article { min-width: 0; padding: 12px 13px; display: grid; grid-template-columns: 9px minmax(0, 1fr); column-gap: 8px; border: 1px solid color-mix(in srgb, var(--accent-light) 14%, transparent); border-radius: 13px; background: color-mix(in srgb, var(--panel-bg) 40%, transparent); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); }
.gate-domain-map i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: color-mix(in srgb, var(--primitive-cool-glow) 70%, transparent); box-shadow: 0 0 12px color-mix(in srgb, var(--primitive-cool-glow) 34%, transparent); }
.gate-domain-map b, .gate-domain-map small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gate-domain-map b { color: var(--content-secondary); font-size: 11.5px; font-weight: 620; }
.gate-domain-map small { grid-column: 2; margin-top: 4px; color: var(--content-faint); font-size: 9.5px; }
.gate-domain-map article.fifth { border-color: color-mix(in srgb, var(--primitive-warm-glow) 25%, transparent); }
.gate-domain-map article.fifth i { background: var(--primitive-warm-glow); box-shadow: 0 0 16px color-mix(in srgb, var(--primitive-warm-glow) 54%, transparent); }
@media (max-width: 900px) { .gate-domain-map { grid-template-columns: repeat(3, minmax(0, 1fr)); } .gate-domain-map article:nth-child(4), .gate-domain-map article:nth-child(5) { grid-column: span 1; } }
@media (max-height: 700px) { .gate-lake { gap: 16px; } .gate-domain-map article { padding: 8px 10px; } .gate-hero { gap: 5px; } .gate-emblem { width: 48px; height: 48px; } .gate-product { font-size: 34px; } }
.gate-pod-row { display: flex; gap: 10px; }
.gate-pod input { flex: 1; height: 52px; padding: 0 16px; border: 1px solid color-mix(in srgb, var(--accent-light) 24%, transparent); border-radius: 14px; outline: 0; color: var(--content-primary); background: rgba(0, 0, 0, .22); font-size: 17px; letter-spacing: .08em; text-align: center; transition: border-color .3s ease, box-shadow .3s ease; }
.gate-pod input:focus { border-color: color-mix(in srgb, var(--primitive-warm-glow) 58%, transparent); box-shadow: 0 0 22px color-mix(in srgb, var(--primitive-warm-glow) 18%, transparent); }

View file

@ -1,7 +1,7 @@
{
"schema": "hololake.current-architecture/v1",
"architecture_id": "HLP-CURRENT-ARCH-001",
"version": "2026-08-16.6",
"version": "2026-08-16.7",
"state": "CURRENT_CANONICAL",
"product": {
"formal_name": "光湖语言系统 · 通用人工智能操作平台",
@ -9,6 +9,25 @@
"english_name": "GuangHu AI Operating System",
"client_name": "HoloLake"
},
"current_native_edge": {
"record_id": "HLP-NATIVE-DOMAIN-MEMBRANE-PNCC-20260816-001",
"state": "SOURCE_IMPLEMENTED_LOCAL_AND_INSTALLED_ACCEPTANCE_PENDING",
"native_source": "product-source/hololake-native-desktop",
"public_entry": "FIVE_DOMAIN_HOME_NUMBER_ROUTED_BEFORE_DOMAIN_LOGIN",
"domain_selection_by_human_allowed": false,
"fifth_domain_registry_owner": "FIFTH_DOMAIN_PRIVATE_ROOT",
"enterprise_four_domain_registry_owner": "ENTERPRISE_ROOT_SERVER",
"enterprise_root_runtime": "DOMAIN_SPECIFIC_GUANGHU_OS_WITH_LINUX_SUBORDINATE_BRIDGE",
"ordinary_user_node_requires_full_guanghu_os_server": false,
"circular_lake_membrane": "STRICT_GLP_EXPRESSION_ADMISSION_BEFORE_LANGUAGE_INBOX",
"accepted_language_grants_execution_authority": false,
"nearby_ai_same_device_discovery": "USER_ONLY_DESCRIPTOR_AND_UNIX_SOCKET_IMPLEMENTED",
"nearby_ai_local_network_discovery": "DEFERRED_PENDING_ENCRYPTION_APPROVAL_REPLAY_PROTECTION_AND_REVOCATION",
"generic_ai_connection": "EXPRESSION_ONLY_VISITOR",
"guanghu_persona_connection": "SEPARATE_BINDING_EVIDENCE_REQUIRED_NOT_YET_IMPLEMENTED",
"user_native_code_channel": "HOLOLAKE_SHELL_OVER_APP_OWNED_GIT_AFTER_DOMAIN_ROUTE_AND_LOGIN",
"forgejo_role": "OPTIONAL_REMOTE_COLLABORATION_ADAPTER_NOT_PRODUCT_SHELL"
},
"read_order": [
"product-source/hololake-platform/architecture/HOLOLAKE-TCS-WRITTEN-WORK-OWNERSHIP-AND-REALITY-ENGINEERING-GATE-20260812.md",
"product-source/hololake-platform/architecture/HOLOLAKE-TCS-UNIVERSAL-LANGUAGE-PERSONAL-CHANNEL-AND-HOST-SELF-ADAPTATION-20260812.md",

View file

@ -1,7 +1,7 @@
{
"schema": "hololake.stage-one-desktop-convergence/v1",
"record_id": "HLP-STAGE-ONE-DESKTOP-CONVERGENCE-001",
"version": "2026-08-14.1",
"version": "2026-08-16.2",
"state": "CURRENT_LOCAL_CONVERGENCE_CONTRACT_SOURCE_NOT_YET_PUBLISHED",
"development_id": "DEV-20260814-003",
"architecture_page": "product-source/hololake-platform/architecture/HOLOLAKE-STAGE-ONE-DESKTOP-CONVERGENCE-AND-CAPABILITY-AUDIT-20260814.md",
@ -30,7 +30,11 @@
],
"keep_foundation": [
"SIGNED_NATIVE_SHELL",
"FIVE_DOMAIN_NUMBER_ROUTER",
"CIRCULAR_LAKE_PROTOCOL_MEMBRANE",
"SAME_DEVICE_NEARBY_AI_DISCOVERY",
"LOCAL_BROKER_AND_RESUMABLE_SESSION",
"USER_NATIVE_GH_PNCC_GIT_CHANNEL",
"PNCC_READ_ONLY_PROJECTION",
"HUMAN_CONFIRMED_REPOSITORY_MOUNT",
"RECEIPTS",