From 96769846ac3db44f8144a1a1f6812d9fd2febc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Mon, 17 Aug 2026 15:41:04 +0800 Subject: [PATCH] feat: expose HoloLake development lane to authenticated carriers --- .../contracts/local-development-bridge.json | 7 +- .../docs/ARCHITECTURE.md | 2 + ...thenticated-development-lane-projection.md | 34 +++ .../scripts/stage-one-platform.test.mjs | 4 + .../src-tauri/src/direct_local_broker.rs | 254 +++++++++++++++++- .../src-tauri/src/local_development_bridge.rs | 17 +- .../hololake-native-desktop/src/main.tsx | 15 +- 7 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 product-source/hololake-native-desktop/docs/adr/0005-authenticated-development-lane-projection.md diff --git a/product-source/hololake-native-desktop/contracts/local-development-bridge.json b/product-source/hololake-native-desktop/contracts/local-development-bridge.json index 4101257a4..6347449bf 100644 --- a/product-source/hololake-native-desktop/contracts/local-development-bridge.json +++ b/product-source/hololake-native-desktop/contracts/local-development-bridge.json @@ -112,12 +112,17 @@ "connector_capability_bootstrap_runtime": true, "installed_app_connector_entry_runtime": true, "external_local_broker_runtime": true, + "authenticated_broker_development_lane_runtime": true, + "development_lane_human_projection_runtime": true, + "visitor_development_lane_rejected": true, "resumable_direct_session_kernel_runtime": true, "single_use_discovery_ticket_runtime": true, "hololake_issued_discovery_ticket_runtime": true, "cross_process_session_event_lock_runtime": true, "direct_session_account_single_writer_runtime": true, "idempotent_session_event_cursor_runtime": true, - "incremental_repository_channel_migrated_to_native_mainline": false + "incremental_repository_channel_migrated_to_native_mainline": false, + "native_general_programming_tool_loop_runtime": false, + "supervised_shell_execution_runtime": false } } diff --git a/product-source/hololake-native-desktop/docs/ARCHITECTURE.md b/product-source/hololake-native-desktop/docs/ARCHITECTURE.md index e34992162..725b31d21 100644 --- a/product-source/hololake-native-desktop/docs/ARCHITECTURE.md +++ b/product-source/hololake-native-desktop/docs/ARCHITECTURE.md @@ -44,6 +44,8 @@ The visible shell is HoloLake itself. Git is the durable history engine below it 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. +An authenticated non-visitor connector may now acquire, inspect and explicitly release the existing account-scoped development write lane through that broker. Account, lane and client instance must match the HoloLake session before the bridge mutates. HoloLake projects the same Rust-owned lane state on the system-details page, so a human can distinguish a nearby expression-only visitor from an active development writer. This is a controlled writer handoff, not a general programming tool loop: shell, file patching, build execution, publication and deployment still require later supervised execution organs and separate authorization receipts. + ## 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. diff --git a/product-source/hololake-native-desktop/docs/adr/0005-authenticated-development-lane-projection.md b/product-source/hololake-native-desktop/docs/adr/0005-authenticated-development-lane-projection.md new file mode 100644 index 000000000..b57860911 --- /dev/null +++ b/product-source/hololake-native-desktop/docs/adr/0005-authenticated-development-lane-projection.md @@ -0,0 +1,34 @@ +# ADR 0005: Authenticated development lane projection + +- Status: accepted for the next HoloLake desktop candidate +- Date: 2026-08-17 + +## Context + +The same-device HoloLake broker can already discover an external AI, open an expression-only visitor session, and resume a HoloLake-issued authenticated session. The local development bridge can already enforce one writer per account, but it is reachable only from the WebView command surface. As a result, an external programming carrier can be visibly connected while still being unable to acquire the HoloLake-owned development lane. The system page also cannot distinguish a connected visitor from an active development writer. + +## Decision + +Expose acquire, inspect, and release operations for the existing local development lane through the user-only Unix broker. Every operation requires a non-visitor HoloLake session. The session account must equal the lane account, the session lane must equal the requested write lane, and the session client instance must equal the requested writer instance. A generic expression-only visitor is rejected before any lane mutation. + +Project the active lane and writer on the HoloLake system-details page by reading the same Rust-owned bridge state. The projection does not create authority and is not a second state store. + +This slice establishes the controlled writer handoff only. It does not yet provide a general shell, file mutation, patch, build, deployment, model, persona binding, or reality-execution engine. + +## Why + +The user needs to see whether development is merely connected or has actually switched into HoloLake's single-writer environment. Reusing the existing session and writer kernels closes that gap without turning socket discovery, MCP, or a visitor message into execution authority. + +## Rejected alternatives + +- Letting any same-device visitor acquire a write lane: discovery and expression are not authorization. +- Maintaining a separate UI-only development status: it would create a second truth source. +- Calling the lane handoff a complete native development container: the programming tool loop and supervised execution engine remain unimplemented. + +## Evidence + +- `src-tauri/src/direct_local_broker.rs` +- `src-tauri/src/direct_local_session.rs` +- `src-tauri/src/local_development_bridge.rs` +- `src/main.tsx` +- `contracts/local-development-bridge.json` diff --git a/product-source/hololake-native-desktop/scripts/stage-one-platform.test.mjs b/product-source/hololake-native-desktop/scripts/stage-one-platform.test.mjs index c38c07440..1ab22376a 100644 --- a/product-source/hololake-native-desktop/scripts/stage-one-platform.test.mjs +++ b/product-source/hololake-native-desktop/scripts/stage-one-platform.test.mjs @@ -27,6 +27,10 @@ test('one account has one writer while readers and reviewers can remain parallel assert.equal(bridge.account_concurrency.parallel_readers_allowed, true) assert.equal(bridge.account_concurrency.parallel_reviewers_allowed, true) assert.equal(bridge.account_concurrency.parallel_editors_allowed, false) + assert.equal(bridge.implementation.authenticated_broker_development_lane_runtime, true) + assert.equal(bridge.implementation.development_lane_human_projection_runtime, true) + assert.equal(bridge.implementation.visitor_development_lane_rejected, true) + assert.equal(bridge.implementation.native_general_programming_tool_loop_runtime, false) }) test('language-space reads are resumable pages rather than silent truncation', () => { diff --git a/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs b/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs index cdcb9b2b9..51390d0e8 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs @@ -1,14 +1,20 @@ use crate::circular_lake_membrane::{receive_at as receive_language_at, ReceiveLanguageInput}; use crate::direct_local_session::{ - append_event_at, authenticate_privileged_at, direct_session_root, issue_ticket_at, open_at, - resume_at, AppendSessionEventInput, AuthenticateSessionInput, DirectSessionReceipt, - IssueDiscoveryTicketInput, OpenSessionInput, ResumeSessionInput, + append_event_at, authenticate_context_at, authenticate_privileged_at, direct_session_root, + issue_ticket_at, open_at, resume_at, AppendSessionEventInput, AuthenticateSessionInput, + AuthenticatedSessionContext, DirectSessionReceipt, IssueDiscoveryTicketInput, OpenSessionInput, + ResumeSessionInput, }; use crate::dynamic_capability_routing::{ install_trusted_registry_at, record_health_at, resolve_at as resolve_capability_route_at, routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput, SignedNodeHealth, }; +use crate::local_development_bridge::{ + account_key_for as development_account_key_for, acquire_at as acquire_development_lane_at, + inspect_at as inspect_development_lane_at, release_at as release_development_lane_at, + AcquireWriteLaneInput, ReleaseWriteLaneInput, +}; use crate::persona_time_authority::{ beijing_time_coordinate, issue_authenticated_at, IssuePersonaTimeTicketInput, }; @@ -119,6 +125,7 @@ struct BrokerStorageRoots { pncc_projection: PathBuf, language_inbox: PathBuf, persona_time: PathBuf, + development: PathBuf, } impl Drop for DirectLocalBrokerHandle { @@ -154,6 +161,9 @@ enum BrokerRequest { QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput), GetBeijingTime, IssuePersonaTimeTicket(IssuePersonaTimeTicketInput), + AcquireDevelopmentWriteLane(AuthenticatedDevelopmentLaneInput), + InspectDevelopmentWriteLane(AuthenticatedDevelopmentInspectInput), + ReleaseDevelopmentWriteLane(AuthenticatedDevelopmentReleaseInput), } #[derive(Clone, Debug, Deserialize)] @@ -232,6 +242,27 @@ struct AuthenticatedPnccProjectionQueryInput { query: QueryPnccReceiptProjectionInput, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct AuthenticatedDevelopmentLaneInput { + session: AuthenticateSessionInput, + lane: AcquireWriteLaneInput, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct AuthenticatedDevelopmentInspectInput { + session: AuthenticateSessionInput, + account_id: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct AuthenticatedDevelopmentReleaseInput { + session: AuthenticateSessionInput, + lane: ReleaseWriteLaneInput, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct BrokerResponse { @@ -408,6 +439,9 @@ fn start_at( .parent() .ok_or("HOLOLAKE_BROKER_PERSONA_TIME_STORAGE_BOUNDARY_INVALID")? .join("persona-time-authority-v1"); + let development_root = development_root_from_session_root(&session_root)?; + fs::create_dir_all(&development_root) + .map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}"))?; if let Some(parent) = socket_path.parent() { fs::create_dir_all(parent) .map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?; @@ -463,6 +497,7 @@ fn start_at( pncc_projection: pncc_projection_root, language_inbox: language_inbox_root, persona_time: persona_time_root, + development: development_root, }, &worker_shutdown, &worker_authenticated_connections, @@ -586,6 +621,7 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse { let pncc_projection_root = &roots.pncc_projection; let language_inbox_root = &roots.language_inbox; let persona_time_root = &roots.persona_time; + let development_root = &roots.development; let request: BrokerRequest = match serde_json::from_slice(bytes) { Ok(request) => request, Err(error) => { @@ -682,6 +718,41 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse { issue_authenticated_at(persona_time_root, session_root, input) .and_then(|ticket| serde_json::to_value(ticket).map_err(|error| error.to_string())) } + BrokerRequest::AcquireDevelopmentWriteLane(input) => { + authenticate_development_request(session_root, &input.session, &input.lane.account_id) + .and_then(|context| { + if context.lane_id != input.lane.lane_id + || context.client_instance_id != input.lane.owner_instance_id + { + return Err("HOLOLAKE_DEVELOPMENT_LANE_SESSION_MISMATCH".into()); + } + acquire_development_lane_at(development_root, input.lane) + }) + .and_then(|receipt| { + serde_json::to_value(receipt).map_err(|error| error.to_string()) + }) + } + BrokerRequest::InspectDevelopmentWriteLane(input) => { + authenticate_development_request(session_root, &input.session, &input.account_id) + .and_then(|_| inspect_development_lane_at(development_root, &input.account_id)) + .and_then(|receipt| { + serde_json::to_value(receipt).map_err(|error| error.to_string()) + }) + } + BrokerRequest::ReleaseDevelopmentWriteLane(input) => { + authenticate_development_request(session_root, &input.session, &input.lane.account_id) + .and_then(|context| { + if context.lane_id != input.lane.lane_id + || context.client_instance_id != input.lane.owner_instance_id + { + return Err("HOLOLAKE_DEVELOPMENT_LANE_SESSION_MISMATCH".into()); + } + release_development_lane_at(development_root, input.lane) + }) + .and_then(|receipt| { + serde_json::to_value(receipt).map_err(|error| error.to_string()) + }) + } }; match result { Ok(value) => BrokerResponse::success(value), @@ -689,6 +760,32 @@ fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse { } } +fn authenticate_development_request( + session_root: &Path, + session: &AuthenticateSessionInput, + account_id: &str, +) -> Result { + let context = authenticate_context_at(session_root, session)?; + if context.account_key != development_account_key_for(account_id)? { + return Err("HOLOLAKE_DEVELOPMENT_ACCOUNT_SESSION_MISMATCH".into()); + } + Ok(context) +} + +fn development_root_from_session_root(session_root: &Path) -> Result { + let account_root = session_root + .parent() + .ok_or("HOLOLAKE_BROKER_DEVELOPMENT_STORAGE_BOUNDARY_INVALID")?; + if session_root.file_name().and_then(|name| name.to_str()) == Some("direct-local-session-v1") { + let app_data = account_root + .parent() + .and_then(Path::parent) + .ok_or("HOLOLAKE_BROKER_DEVELOPMENT_STORAGE_BOUNDARY_INVALID")?; + return Ok(app_data.join("local-development-bridge-v1")); + } + Ok(account_root.join("local-development-bridge-v1")) +} + fn open_visitor_session_at( session_root: &Path, input: OpenVisitorSessionInput, @@ -1152,6 +1249,157 @@ mod tests { assert_eq!(verified["result"]["returnedEventCount"], 0); } + #[test] + fn authenticated_connector_can_acquire_and_inspect_the_hololake_development_lane() { + 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("accounts-v1/test-account/direct-local-session-v1"); + let routes = temp.path().join("routes"); + fs::create_dir_all(&sessions).unwrap(); + fs::create_dir_all(&routes).unwrap(); + let _broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap(); + let ticket = issue_ticket_at( + &sessions, + IssueDiscoveryTicketInput { + account_id: "human-1".into(), + lane_id: "hololake-zero-core-runtime-20260817".into(), + client_instance_id: "codex-1".into(), + }, + ) + .unwrap(); + let opened = request( + &socket, + serde_json::json!({ + "operation": "OPEN_SESSION", + "input": { + "accountId": "human-1", + "laneId": "hololake-zero-core-runtime-20260817", + "clientInstanceId": "codex-1", + "discoveryTicket": ticket.discovery_ticket + } + }), + ); + let session = serde_json::json!({ + "accountId": "human-1", + "sessionId": opened["result"]["sessionId"], + "resumeSecret": opened["result"]["resumeSecret"] + }); + + let acquired = request( + &socket, + serde_json::json!({ + "operation": "ACQUIRE_DEVELOPMENT_WRITE_LANE", + "input": { + "session": session, + "lane": { + "accountId": "human-1", + "laneId": "hololake-zero-core-runtime-20260817", + "ownerInstanceId": "codex-1", + "resumeToken": null + } + } + }), + ); + assert_eq!(acquired["ok"], true); + assert_eq!(acquired["result"]["state"], "ACQUIRED"); + + let inspected = request( + &socket, + serde_json::json!({ + "operation": "INSPECT_DEVELOPMENT_WRITE_LANE", + "input": { + "session": session, + "accountId": "human-1" + } + }), + ); + assert_eq!(inspected["ok"], true); + assert_eq!(inspected["result"]["state"], "ACTIVE"); + assert_eq!( + inspected["result"]["laneId"], + "hololake-zero-core-runtime-20260817" + ); + assert_eq!(inspected["result"]["ownerInstanceId"], "codex-1"); + + let released = request( + &socket, + serde_json::json!({ + "operation": "RELEASE_DEVELOPMENT_WRITE_LANE", + "input": { + "session": session, + "lane": { + "accountId": "human-1", + "laneId": "hololake-zero-core-runtime-20260817", + "ownerInstanceId": "codex-1", + "resumeToken": acquired["result"]["resumeToken"] + } + } + }), + ); + assert_eq!(released["ok"], true); + assert_eq!(released["result"]["state"], "RELEASED"); + + let available = request( + &socket, + serde_json::json!({ + "operation": "INSPECT_DEVELOPMENT_WRITE_LANE", + "input": { + "session": session, + "accountId": "human-1" + } + }), + ); + assert_eq!(available["result"]["state"], "AVAILABLE"); + } + + #[test] + fn expression_only_visitor_cannot_acquire_a_development_lane() { + 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("accounts-v1/test-account/direct-local-session-v1"); + 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 opened = request( + &socket, + serde_json::json!({ + "operation": "OPEN_VISITOR_SESSION", + "input": {"clientInstanceId": "generic-ai-1"} + }), + ); + let acquired = request( + &socket, + serde_json::json!({ + "operation": "ACQUIRE_DEVELOPMENT_WRITE_LANE", + "input": { + "session": { + "accountId": opened["result"]["accountId"], + "sessionId": opened["result"]["session"]["sessionId"], + "resumeSecret": opened["result"]["session"]["resumeSecret"] + }, + "lane": { + "accountId": opened["result"]["accountId"], + "laneId": "unauthorized-development", + "ownerInstanceId": "generic-ai-1", + "resumeToken": null + } + } + }), + ); + assert_eq!(acquired["ok"], false); + assert_eq!( + acquired["error"], + "HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY" + ); + } + #[test] fn only_an_authenticated_persistent_connector_counts_as_online() { let temp = TempDir::new().unwrap(); diff --git a/product-source/hololake-native-desktop/src-tauri/src/local_development_bridge.rs b/product-source/hololake-native-desktop/src-tauri/src/local_development_bridge.rs index 90d5cc167..d99cb7f92 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/local_development_bridge.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/local_development_bridge.rs @@ -116,7 +116,10 @@ fn bridge_root(app: &AppHandle) -> Result { .map_err(|error| format!("HOLOLAKE_BRIDGE_STORAGE_UNAVAILABLE: {error}")) } -fn acquire_at(root: &Path, input: AcquireWriteLaneInput) -> Result { +pub(crate) fn acquire_at( + root: &Path, + input: AcquireWriteLaneInput, +) -> Result { validate_identifier(&input.account_id, "ACCOUNT")?; validate_identifier(&input.lane_id, "LANE")?; validate_identifier(&input.owner_instance_id, "OWNER")?; @@ -186,7 +189,7 @@ fn resume_existing( Ok(receipt_for("RESUMED", &record, None, observed_at)) } -fn inspect_at(root: &Path, account_id: &str) -> Result { +pub(crate) fn inspect_at(root: &Path, account_id: &str) -> Result { validate_identifier(account_id, "ACCOUNT")?; let account_key = sha256_hex(account_id.as_bytes()); let active_path = root @@ -217,7 +220,10 @@ fn inspect_at(root: &Path, account_id: &str) -> Result Result { +pub(crate) fn release_at( + root: &Path, + input: ReleaseWriteLaneInput, +) -> Result { validate_identifier(&input.account_id, "ACCOUNT")?; validate_identifier(&input.lane_id, "LANE")?; validate_identifier(&input.owner_instance_id, "OWNER")?; @@ -339,6 +345,11 @@ fn sha256_hex(value: &[u8]) -> String { .collect() } +pub(crate) fn account_key_for(account_id: &str) -> Result { + validate_identifier(account_id, "ACCOUNT")?; + Ok(sha256_hex(account_id.as_bytes())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/product-source/hololake-native-desktop/src/main.tsx b/product-source/hololake-native-desktop/src/main.tsx index 4337ed5e3..91d1f04e4 100644 --- a/product-source/hololake-native-desktop/src/main.tsx +++ b/product-source/hololake-native-desktop/src/main.tsx @@ -62,6 +62,13 @@ interface HomeStatus { releaseRecoveryState: string mcpRole: string } +interface DevelopmentWriteLaneStatus { + state: 'AVAILABLE' | 'ACTIVE' + laneId?: string + ownerInstanceId?: string + acquiredAtUnixMs?: number + observedAtUnixMs: number +} interface PersonalChannelIdentity { humanSubjectId: string; displayName: string; channelId: string; createdAtUnixMs: number } interface PersonalChannelModule { moduleId: string; kind: string; displayName: string; state: string; installedAtUnixMs: number; timeZone: string; calendarName: string; clockVerification: string } interface LoginSession { username: string; host: string; domain: string; signedInAtUnixMs: number } @@ -573,6 +580,7 @@ function HoloLakeApp() { const [serverPncc, setServerPncc] = useState(null) const [serverPnccBusy, setServerPnccBusy] = useState(false) const [serverPnccReadback, setServerPnccReadback] = useState<'WAITING' | 'LIVE' | 'UNAVAILABLE'>('WAITING') + const [developmentLane, setDevelopmentLane] = useState(null) const [enterpriseEntry, setEnterpriseEntry] = useState(null) const [enterpriseEntryBusy, setEnterpriseEntryBusy] = useState(false) const [enterpriseReceiptBusy, setEnterpriseReceiptBusy] = useState(false) @@ -600,12 +608,14 @@ function HoloLakeApp() { const startup = await invoke('start_persona_time_authority') setBeijingCoordinate(startup.coordinate) } catch { /* 联网失败时其余首页能力仍可启动。 */ } - const [homeResult, personalResult, knowledgeResult, codeResult, eraResult] = await Promise.allSettled([ + const localAccountId = getOrCreateLocalId('hololake-local-account', 'human-local') + const [homeResult, personalResult, knowledgeResult, codeResult, eraResult, developmentResult] = await Promise.allSettled([ invoke('get_hololake_home_status'), invoke('get_personal_channel_snapshot'), invoke('get_knowledge_snapshot'), invoke('get_code_channel_snapshot'), invoke('get_guanghu_era_timeline'), + invoke('inspect_development_write_lane', { input: { accountId: localAccountId } }), ]) if (homeResult.status === 'fulfilled') setStatus(homeResult.value) if (personalResult.status === 'fulfilled') setPersonal(personalResult.value) @@ -615,6 +625,7 @@ function HoloLakeApp() { setEraTimeline(eraResult.value) setBeijingCoordinate(eraResult.value.currentCoordinate) } + setDevelopmentLane(developmentResult.status === 'fulfilled' ? developmentResult.value : null) }, []) const loadReceipts = useCallback(async () => { try { @@ -1321,7 +1332,7 @@ function HoloLakeApp() { :

当前无法取得京东主控的实时回读。界面不会以静态记录替代在线状态。

} -

本机 GH-PNCC 投影

本机仓库挂载与读取回执来自同一运行事件流。

已挂载仓库
{status.codeRepositoryMountCount}
投影回执
{status.pnccReceiptCount}
当前结论
{status.codeRepositoryMountCount > 0 && status.pnccReceiptCount > 0 ? '已有可核验运行记录' : '接口已接入,尚无完整运行记录'}
+

本机 GH-PNCC 投影

本机仓库挂载、开发写入通道与读取回执来自同一运行事件流。

已挂载仓库
{status.codeRepositoryMountCount}
投影回执
{status.pnccReceiptCount}
开发执行通道
{developmentLane?.state === 'ACTIVE' ? '已切入 HoloLake' : '等待受控载体'}
当前开发线
{developmentLane?.laneId || '—'}
当前写入载体
{developmentLane?.ownerInstanceId || '—'}
当前结论
{developmentLane?.state === 'ACTIVE' ? '开发执行环境已由 HoloLake 持有单写通道' : status.codeRepositoryMountCount > 0 && status.pnccReceiptCount > 0 ? '已有可核验运行记录' : '接口已接入,尚无完整运行记录'}

用户编号验证

用户编号必须通过登记服务验证。验证结果不构成人格绑定、模型载体绑定或执行授权。

{zeroPoint ? (zeroPoint.route === 'verified' ? '验证有效' : '功能受限') : '正在读取'}