feat: admit signed mobile sync bridge module
This commit is contained in:
parent
eb7223ea4c
commit
292889f934
21 changed files with 1891 additions and 15 deletions
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"schema": "hololake.mobile-sync.contract/v1",
|
||||
"record_id": "HLP-MOBILE-SYNC-001",
|
||||
"state": "HOLOLAKE_0_5_NUMBERED_DESKTOP_BRIDGE_ACCEPTED",
|
||||
"package": {
|
||||
"official_module_number": "HLP-MOD-OFFICIAL-MOBILE-SYNC-0001",
|
||||
"adapter": "mobile-sync-v1",
|
||||
"registration_class": "OFFICIAL_LIGHTHOUSE",
|
||||
"activation": "SIGNED_PACKAGE_PLUS_EXPLICIT_HUMAN_PERMISSION_CONFIRMATION",
|
||||
"listener_after_activation": "EXPLICIT_HUMAN_ACTION_ONLY"
|
||||
},
|
||||
"numbered_ipc": {
|
||||
"module": "HLP-NIPC-MOD-0030",
|
||||
"target": "HLP-NIPC-TGT-0030",
|
||||
"operations": "HLP-NIPC-OP-0141..HLP-NIPC-OP-0146",
|
||||
"public_tauri_commands": ["numbered_ipc"],
|
||||
"mismatched_coordinate": "FAIL_CLOSED"
|
||||
},
|
||||
"desktop_role": "USER_LOCAL_COMPUTER_TERMINAL_ROOT_NODE",
|
||||
"mobile_role": "REMOTE_BODY_ENTRY_OF_THE_SAME_PERSONA_SYSTEM",
|
||||
"transport": {
|
||||
"implemented": "SAME_LAN_DIRECT_HTTP_WITH_APPLICATION_LAYER_ENCRYPTION",
|
||||
"port_preference": 37421,
|
||||
"remote_internet_direct": "NOT_IMPLEMENTED",
|
||||
"encrypted_relay": "NOT_IMPLEMENTED",
|
||||
"maximum_request_bytes": 262144,
|
||||
"maximum_concurrent_connections": 16,
|
||||
"duplicate_http_header": "REJECT"
|
||||
},
|
||||
"pairing": {
|
||||
"uri_scheme": "hololake://pair",
|
||||
"secret_bits": 256,
|
||||
"ttl_seconds": 600,
|
||||
"single_use": true,
|
||||
"payload_aead": "CHACHA20_POLY1305",
|
||||
"aad": "hololake.mobile.pair/v1"
|
||||
},
|
||||
"session": {
|
||||
"key_bits": 256,
|
||||
"aead": "CHACHA20_POLY1305",
|
||||
"replay_guard": "STRICTLY_INCREASING_PER_DEVICE_COUNTER",
|
||||
"device_revocation": true,
|
||||
"desktop_key_storage": "ACCOUNT_SCOPED_SQLITE",
|
||||
"ios_key_storage": "KEYCHAIN_THIS_DEVICE_ONLY"
|
||||
},
|
||||
"routes": {
|
||||
"GET /v1/status": "NO_PRIVATE_PAYLOAD",
|
||||
"POST /v1/pair": "ONE_TIME_PAIRING_SECRET_REQUIRED",
|
||||
"POST /v1/sync": "PAIRED_DEVICE_AEAD_AND_COUNTER_REQUIRED"
|
||||
},
|
||||
"projection": {
|
||||
"personal_channel": "MINIMUM_COUNTS_AND_INTEGRITY",
|
||||
"web_novel": "WORK_LIST_AND_COUNTS",
|
||||
"education": "COUNTS_ONLY_NO_SENSITIVE_CELL_VALUES",
|
||||
"mobile_capture": "BOUNDED_INBOX_WRITE_ONLY_NO_AUTOMATIC_DOMAIN_MUTATION"
|
||||
},
|
||||
"hard_boundaries": {
|
||||
"remote_desktop_clone": false,
|
||||
"second_persona_system": false,
|
||||
"platform_private_payload_custody": false,
|
||||
"desktop_offline_execution": false,
|
||||
"mobile_capture_mutates_persona_or_industry_data": false,
|
||||
"module_unmount_stops_listener_before_lifecycle_transition": true,
|
||||
"background_frontend_polling": false
|
||||
},
|
||||
"client_scope": {
|
||||
"desktop_bridge": "IN_THIS_ADMISSION",
|
||||
"ios_application_source": "NOT_IN_DECLARED_DONOR_CANDIDATE",
|
||||
"ios_installable_package": "PENDING_SEPARATE_ADMISSION",
|
||||
"claim_real_iphone_end_to_end_accepted": false
|
||||
},
|
||||
"current_acceptance": {
|
||||
"state": "PASS_DESKTOP_BRIDGE_ONLY",
|
||||
"module_package_sha256": "61a9d402c936d7477609bcfa6eb4ad6e83cb6c089ba51b3db09a2d6259185924",
|
||||
"module_package_signature": "PASS_EMBEDDED_PRODUCT_TRUST",
|
||||
"signed_app_binary_sha256": "9be06d6c5dbd4f46f6f925142d633f12b42ee553873c2342f9804bcf39dde7b4",
|
||||
"signed_app_cdhash": "c595cb2a7729d49638faa78f76e145ee9b1da05a",
|
||||
"apple_team_identifier": "825A9L3G7Q",
|
||||
"module_receipts": {
|
||||
"install": "a09496ec6113254f77ea2425d0bd30571e9e0e3df62bfa18e500f860c2f65d4d",
|
||||
"mount": "5b162ee807c8b4d9c8aa8d9cb590c64587f51067dc0804030bf57ed0753ac25a",
|
||||
"self_test_pass": "e66ee868e28a25fcd70cc888845661b5044d18a6e18b70cee6e4839b02fe1c53"
|
||||
},
|
||||
"desktop_listener": "PASS_EXPLICIT_START_REACHABLE_STATUS_AND_EXPLICIT_STOP",
|
||||
"restart_restore": "PASS_ACTIVE_MODULE_RESTORED_LISTENER_OFFLINE",
|
||||
"legacy_account_readback": "PASS_ONE_PAIRED_DEVICE_ONE_ISOLATED_CAPTURE_RETAINED",
|
||||
"live_iphone_pairing_this_cycle": "NOT_CLAIMED",
|
||||
"notarization": "FINAL_RELEASE_CANDIDATE_PENDING"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"schema": "hololake.module-donor-admission-registry/v1",
|
||||
"record_id": "HLP-MODULE-DONOR-ADMISSION-001",
|
||||
"state": "FIVE_CANDIDATES_ADMITTED_REMAINING_DONORS_QUARANTINED",
|
||||
"state": "SIX_CANDIDATES_ADMITTED_REMAINING_DONORS_QUARANTINED",
|
||||
"root_rule": {
|
||||
"official_base": "HOLOLAKE_0.5.0_NUMBERED_IPC_ROOT",
|
||||
"repair_old_application_in_place": false,
|
||||
|
|
@ -151,12 +151,16 @@
|
|||
"admission_order": 6,
|
||||
"candidate_number": "HLP-DONOR-CAND-0006",
|
||||
"name": "mobile_sync",
|
||||
"state": "QUARANTINED_PENDING_ADMISSION",
|
||||
"state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
|
||||
"paths": [
|
||||
"contracts/mobile-sync-v1.json",
|
||||
"src/MobileSyncPanel.tsx",
|
||||
"src-tauri/src/mobile_sync.rs"
|
||||
]
|
||||
],
|
||||
"runtime_module_number": "HLP-MOD-OFFICIAL-MOBILE-SYNC-0001",
|
||||
"numbered_ipc_module": "HLP-NIPC-MOD-0030",
|
||||
"acceptance_evidence": "contracts/mobile-sync-v1.json#current_acceptance",
|
||||
"boundary": "DESKTOP_ROOT_NODE_SAME_LAN_BRIDGE_ONLY; EXPLICIT_LISTENER; IOS_INSTALLABLE_PACKAGE_PENDING_SEPARATE_ADMISSION; NO_SECOND_PERSONA_SYSTEM"
|
||||
},
|
||||
{
|
||||
"admission_order": 7,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,12 @@
|
|||
"import_education_tables_from_dialog",
|
||||
"get_education_recognition_capability",
|
||||
"get_web_novel_workspace_snapshot",
|
||||
"inspect_web_novel_document_from_dialog"
|
||||
"inspect_web_novel_document_from_dialog",
|
||||
"start_mobile_sync",
|
||||
"get_mobile_sync_status",
|
||||
"rotate_mobile_pairing",
|
||||
"stop_mobile_sync",
|
||||
"get_mobile_sync_snapshot"
|
||||
],
|
||||
"input_wrapper_aliases": [
|
||||
"confirm_hololake_update_install",
|
||||
|
|
@ -169,7 +174,8 @@
|
|||
"upsert_web_novel_timeline_event",
|
||||
"link_web_novel_scene_entity",
|
||||
"restore_web_novel_chapter_version",
|
||||
"export_web_novel_author_delivery"
|
||||
"export_web_novel_author_delivery",
|
||||
"revoke_mobile_sync_device"
|
||||
],
|
||||
"direct_field_aliases": {
|
||||
"perform_code_repo_login": [
|
||||
|
|
@ -361,6 +367,11 @@
|
|||
"module_number": "HLP-NIPC-MOD-0029",
|
||||
"target_number": "HLP-NIPC-TGT-0029",
|
||||
"internal_name": "web_novel_delivery"
|
||||
},
|
||||
{
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"internal_name": "mobile_sync"
|
||||
}
|
||||
],
|
||||
"operations": [
|
||||
|
|
@ -1903,6 +1914,72 @@
|
|||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/export_web_novel_author_delivery/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0141",
|
||||
"alias": "start_mobile_sync",
|
||||
"handler": "mobile_sync::start_mobile_sync",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/start_mobile_sync/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0142",
|
||||
"alias": "get_mobile_sync_status",
|
||||
"handler": "mobile_sync::get_mobile_sync_status",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_mobile_sync_status/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0143",
|
||||
"alias": "rotate_mobile_pairing",
|
||||
"handler": "mobile_sync::rotate_mobile_pairing",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/rotate_mobile_pairing/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0144",
|
||||
"alias": "stop_mobile_sync",
|
||||
"handler": "mobile_sync::stop_mobile_sync",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/stop_mobile_sync/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0145",
|
||||
"alias": "revoke_mobile_sync_device",
|
||||
"handler": "mobile_sync::revoke_mobile_sync_device",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/revoke_mobile_sync_device/v1"
|
||||
},
|
||||
{
|
||||
"operation_number": "HLP-NIPC-OP-0146",
|
||||
"alias": "get_mobile_sync_snapshot",
|
||||
"handler": "mobile_sync::get_mobile_sync_snapshot",
|
||||
"channel_number": "HLP-NIPC-CH-0002",
|
||||
"module_number": "HLP-NIPC-MOD-0030",
|
||||
"target_number": "HLP-NIPC-TGT-0030",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"payload_schema": "hololake.numbered-ipc.payload/get_mobile_sync_snapshot/v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"schema": "hololake.module-package/v1",
|
||||
"manifest": {
|
||||
"moduleNumber": "HLP-MOD-OFFICIAL-MOBILE-SYNC-0001",
|
||||
"registrationClass": "OFFICIAL_LIGHTHOUSE",
|
||||
"displayName": "移动同步桥",
|
||||
"version": "0.1.0",
|
||||
"minimumHostVersion": "0.5.0",
|
||||
"adapter": "mobile-sync-v1",
|
||||
"contentDigest": "3e3ff8c7beba3d70ee01c918630a40ea901313b424f264b41e2d376550534e6e",
|
||||
"permissions": ["MOBILE_SYNC_LISTEN_SAME_LAN", "MOBILE_SYNC_PAIR_DEVICE", "MOBILE_SYNC_READ_MINIMUM_PROJECTION", "MOBILE_SYNC_CAPTURE_INBOX_WRITE", "MOBILE_SYNC_REVOKE_DEVICE"],
|
||||
"userDataSchema": "hololake.module-data/mobile-sync/v1",
|
||||
"selfTest": {
|
||||
"kind": "DECLARATIVE_SCHEMA_V1",
|
||||
"expectedContentDigest": "3e3ff8c7beba3d70ee01c918630a40ea901313b424f264b41e2d376550534e6e"
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"entry": "mobile-sync",
|
||||
"adapterConfig": {
|
||||
"transport": "SAME_LAN_DIRECT_HTTP_WITH_APPLICATION_LAYER_ENCRYPTION",
|
||||
"preferredPort": 37421,
|
||||
"pairingTtlSeconds": 600,
|
||||
"maximumRequestBytes": 262144,
|
||||
"maximumConcurrentConnections": 16,
|
||||
"desktopRole": "USER_LOCAL_COMPUTER_TERMINAL_ROOT_NODE",
|
||||
"mobileRole": "REMOTE_BODY_ENTRY_OF_THE_SAME_PERSONA_SYSTEM",
|
||||
"desktopOfflineExecution": false,
|
||||
"platformPrivatePayloadCustody": false,
|
||||
"automaticDomainMutation": false,
|
||||
"iosClientPackaging": "PENDING_SEPARATE_ADMISSION"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVReHJHQ2hTVDYvRTV5bDhHMkZjMzNkN0lpaDBLaDRxcnhHajJ3b3NDS21zZWljYWJORmtrNit5MHJvZC9yN2YrSUhiSE5sT0dMd25Jc2pXMkQyeWttODhKSklsRmVKNWd3PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzg3MDgzMTYwCWZpbGU6SExQLU1PRC1PRkZJQ0lBTC1NT0JJTEUtU1lOQy0wMDAxLTAuMS4wLmdobW9kCjN6R1JZZitSbzI1cVRESkNUMnJrVXFWUlUyZ3B3L2lIQmhrOXZaTGc3T2x1REdNUUZrK2M4WWs4bE9Zb3NuSTNoNmRBRmlCekh0dkNNN1pKR2k4ZkFRPT0K
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
},
|
||||
{
|
||||
"recordId": "HLP-NUMBERED-IPC-ROOT-001",
|
||||
"sha256": "06516d366a37a6e2f680c225b43920919e04db3603c2c1f11bd256b320631465"
|
||||
"sha256": "40a9d411dc891c8d3333dfea3f9d24c1047c8ae8568c04a83263bb4be5c6a466"
|
||||
},
|
||||
{
|
||||
"recordId": "HLP-NBROKER-ROOT-001",
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
"everyAcceptedCallHasEvidenceClass": true,
|
||||
"mismatchedCoordinate": "FAIL_CLOSED"
|
||||
},
|
||||
"routeCount": 162,
|
||||
"routeCount": 168,
|
||||
"routes": [
|
||||
{
|
||||
"transport": "DIRECT_LOCAL_NUMBERED_BROKER",
|
||||
|
|
@ -1947,6 +1947,90 @@
|
|||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0029/HLP-NIPC-OP-0140/HLP-NIPC-TGT-0029"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0141",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "start_mobile_sync",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0141/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0142",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "get_mobile_sync_status",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0142/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0143",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "rotate_mobile_pairing",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0143/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0144",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "stop_mobile_sync",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0144/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0145",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "revoke_mobile_sync_device",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "STATE_CHANGE",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0145/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0146",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030",
|
||||
"alias": "get_mobile_sync_snapshot",
|
||||
"admission": "VERIFIED_HUMAN_ROUTE",
|
||||
"effect": "READ_OR_STATUS",
|
||||
"evidence": "HASH_CHAINED_NUMBERED_IPC_RECEIPT",
|
||||
"path": "HLP-NUMBER-WORLD-ROOT-001/TAURI/HLP-NIPC-CH-0002/HLP-NIPC-MOD-0030/HLP-NIPC-OP-0146/HLP-NIPC-TGT-0030"
|
||||
},
|
||||
{
|
||||
"transport": "TAURI_WEBVIEW_NUMBERED_IPC",
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8')
|
||||
const contract = JSON.parse(read('contracts/mobile-sync-v1.json'))
|
||||
const registry = JSON.parse(read('contracts/numbered-ipc-registry.json'))
|
||||
const modulePackage = JSON.parse(read('fixtures/module-packages/HLP-MOD-OFFICIAL-MOBILE-SYNC-0001-0.1.0.ghmod'))
|
||||
const rust = read('src-tauri/src/mobile_sync.rs')
|
||||
const frontend = read('src/modules/mobile-sync/index.tsx')
|
||||
|
||||
test('mobile sync is a signed opt-in bridge, not a second persona system', () => {
|
||||
assert.equal(modulePackage.manifest.moduleNumber, 'HLP-MOD-OFFICIAL-MOBILE-SYNC-0001')
|
||||
assert.equal(modulePackage.manifest.registrationClass, 'OFFICIAL_LIGHTHOUSE')
|
||||
assert.equal(modulePackage.manifest.adapter, 'mobile-sync-v1')
|
||||
assert.equal(modulePackage.manifest.permissions.length, 5)
|
||||
assert.equal(modulePackage.payload.adapterConfig.desktopRole, 'USER_LOCAL_COMPUTER_TERMINAL_ROOT_NODE')
|
||||
assert.equal(modulePackage.payload.adapterConfig.iosClientPackaging, 'PENDING_SEPARATE_ADMISSION')
|
||||
assert.equal(contract.hard_boundaries.second_persona_system, false)
|
||||
assert.equal(contract.hard_boundaries.desktop_offline_execution, false)
|
||||
})
|
||||
|
||||
test('all desktop controls cross one numbered route family', () => {
|
||||
const routes = registry.operations.filter((route) => route.module_number === 'HLP-NIPC-MOD-0030')
|
||||
assert.deepEqual(routes.map((route) => route.operation_number), Array.from({ length: 6 }, (_, index) => `HLP-NIPC-OP-${String(index + 141).padStart(4, '0')}`))
|
||||
assert.ok(routes.every((route) => route.target_number === 'HLP-NIPC-TGT-0030'))
|
||||
assert.ok(routes.every((route) => route.admission === 'VERIFIED_HUMAN_ROUTE'))
|
||||
assert.doesNotMatch(frontend, /from ['"]@tauri-apps\/api\/core['"]/)
|
||||
assert.doesNotMatch(rust, /#\[tauri::command\]/)
|
||||
assert.match(rust, /require_active_module_adapter/)
|
||||
})
|
||||
|
||||
test('transport is bounded, encrypted, replay guarded and explicitly controlled', () => {
|
||||
assert.match(rust, /MAX_REQUEST_BYTES: usize = 256 \* 1024/)
|
||||
assert.match(rust, /MAX_ACTIVE_CONNECTIONS: usize = 16/)
|
||||
assert.match(rust, /CHACHA20_POLY1305/)
|
||||
assert.match(rust, /input\.counter <= last_counter/)
|
||||
assert.match(rust, /DUPLICATE_HEADER_REJECTED/)
|
||||
assert.match(rust, /pub\(crate\) fn stop_for_unmount/)
|
||||
assert.doesNotMatch(frontend, /setInterval/)
|
||||
assert.match(frontend, /开启 iPhone 同步/)
|
||||
assert.match(frontend, /关闭本机同步入口/)
|
||||
})
|
||||
|
|
@ -7,8 +7,8 @@ test('identity, webview and direct broker numbers compile into one unique eviden
|
|||
const generated = JSON.parse(readFileSync(new URL('../generated/unified-number-coordinate-tree.json', import.meta.url), 'utf8'))
|
||||
assert.deepEqual(generated, compileUnifiedNumberTree())
|
||||
assert.equal(generated.recordId, 'HLP-UNIFIED-NUMBER-TREE-001')
|
||||
assert.equal(generated.routeCount, 162)
|
||||
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 162)
|
||||
assert.equal(generated.routeCount, 168)
|
||||
assert.equal(new Set(generated.routes.map((route) => route.path)).size, 168)
|
||||
assert.equal(generated.invariants.everyPhysicalCallHasNumberedRoute, true)
|
||||
assert.equal(generated.invariants.everyAcceptedCallHasEvidenceClass, true)
|
||||
assert.ok(generated.routes.every((route) => route.admission && route.evidence))
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"interprocess",
|
||||
"minisign-verify",
|
||||
"qrcode",
|
||||
"quick-xml 0.31.0",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
|
@ -3043,6 +3044,12 @@ dependencies = [
|
|||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcode"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ zip = { version = "=0.6.6", default-features = false, features = ["deflate"] }
|
|||
tokio = { version = "1", features = ["time"] }
|
||||
futures-util = "0.3"
|
||||
minisign-verify = "0.2.5"
|
||||
qrcode = { version = "0.14", default-features = false, features = ["svg"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
widestring = "1"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ mod home_status;
|
|||
mod knowledge_base;
|
||||
mod local_development_bridge;
|
||||
mod metacognitive_zero_layer;
|
||||
mod mobile_sync;
|
||||
mod module_package_runtime;
|
||||
mod native_composition;
|
||||
mod number_coordinate_tree;
|
||||
|
|
@ -53,6 +54,7 @@ pub fn run() {
|
|||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.manage(numbered_ipc::NumberedIpcState::default())
|
||||
.manage(mobile_sync::MobileSyncState::default())
|
||||
.invoke_handler(tauri::generate_handler![numbered_ipc::numbered_ipc,])
|
||||
.setup(|app| {
|
||||
// GLS 是 HoloLake 产品内核,不是开发机旁路服务。合同、依赖闭包、
|
||||
|
|
|
|||
1227
product-source/hololake-native-desktop/src-tauri/src/mobile_sync.rs
Normal file
1227
product-source/hololake-native-desktop/src-tauri/src/mobile_sync.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -87,6 +87,12 @@ const WEB_NOVEL_DELIVERY_PACKAGE: &[u8] = include_bytes!(
|
|||
const WEB_NOVEL_DELIVERY_SIGNATURE: &str = include_str!(
|
||||
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-WEB-NOVEL-DELIVERY-0001-0.1.0.ghmod.sig"
|
||||
);
|
||||
const MOBILE_SYNC_NUMBER: &str = "HLP-MOD-OFFICIAL-MOBILE-SYNC-0001";
|
||||
const MOBILE_SYNC_PACKAGE: &[u8] =
|
||||
include_bytes!("../../fixtures/module-packages/HLP-MOD-OFFICIAL-MOBILE-SYNC-0001-0.1.0.ghmod");
|
||||
const MOBILE_SYNC_SIGNATURE: &str = include_str!(
|
||||
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-MOBILE-SYNC-0001-0.1.0.ghmod.sig"
|
||||
);
|
||||
|
||||
struct BundledModuleSource {
|
||||
module_number: &'static str,
|
||||
|
|
@ -140,6 +146,11 @@ const BUNDLED_MODULES: &[BundledModuleSource] = &[
|
|||
package: WEB_NOVEL_DELIVERY_PACKAGE,
|
||||
signature: WEB_NOVEL_DELIVERY_SIGNATURE,
|
||||
},
|
||||
BundledModuleSource {
|
||||
module_number: MOBILE_SYNC_NUMBER,
|
||||
package: MOBILE_SYNC_PACKAGE,
|
||||
signature: MOBILE_SYNC_SIGNATURE,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -1209,6 +1220,9 @@ pub async fn unmount_module(
|
|||
app: AppHandle,
|
||||
input: ModuleNumberInput,
|
||||
) -> Result<ModuleMutationReceipt, String> {
|
||||
if input.module_number == MOBILE_SYNC_NUMBER {
|
||||
crate::mobile_sync::stop_for_unmount(&app)?;
|
||||
}
|
||||
transition(
|
||||
&runtime_root(&app)?,
|
||||
&input.module_number,
|
||||
|
|
@ -1585,7 +1599,7 @@ mod tests {
|
|||
assert_eq!(package.manifest.adapter, "channel-workbench-v1");
|
||||
assert_eq!(package.manifest.permissions.len(), 4);
|
||||
assert!(is_sha256(&digest));
|
||||
assert_eq!(BUNDLED_MODULES.len(), 9);
|
||||
assert_eq!(BUNDLED_MODULES.len(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1670,6 +1684,29 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundled_mobile_sync_is_signed_and_keeps_the_desktop_as_root_node() {
|
||||
let (_, package, digest) = read_verified_package_bytes(
|
||||
MOBILE_SYNC_PACKAGE,
|
||||
MOBILE_SYNC_SIGNATURE,
|
||||
RELEASE_TRUST_RAW,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(package.manifest.module_number, MOBILE_SYNC_NUMBER);
|
||||
assert_eq!(package.manifest.registration_class, "OFFICIAL_LIGHTHOUSE");
|
||||
assert_eq!(package.manifest.adapter, "mobile-sync-v1");
|
||||
assert_eq!(package.manifest.permissions.len(), 5);
|
||||
assert_eq!(
|
||||
package.payload["adapterConfig"]["desktopRole"],
|
||||
"USER_LOCAL_COMPUTER_TERMINAL_ROOT_NODE"
|
||||
);
|
||||
assert_eq!(
|
||||
package.payload["adapterConfig"]["iosClientPackaging"],
|
||||
"PENDING_SEPARATE_ADMISSION"
|
||||
);
|
||||
assert!(is_sha256(&digest));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn symlinked_package_input_is_rejected_before_signature_processing() {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ fn validate_tree() -> Result<(), String> {
|
|||
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|
||||
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|
||||
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|
||||
|| tree.route_count != 162
|
||||
|| tree.route_count != 168
|
||||
|| tree.routes.len() != tree.route_count
|
||||
|| !tree.invariants.number_is_stable_coordinate_not_authority
|
||||
|| !tree.invariants.path_is_unique_navigation
|
||||
|
|
|
|||
|
|
@ -934,7 +934,7 @@ mod tests {
|
|||
#[test]
|
||||
fn registry_is_closed_and_contains_every_migrated_command() {
|
||||
let registry = load_registry().unwrap();
|
||||
assert_eq!(registry.operations.len(), 140);
|
||||
assert_eq!(registry.operations.len(), 146);
|
||||
assert!(!registry.runtime.legacy_direct_commands_allowed);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,20 @@ pub(crate) async fn dispatch(
|
|||
"module_package_runtime::activate_bundled_module" => json(
|
||||
crate::module_package_runtime::activate_bundled_module(app, input(&payload)?).await?,
|
||||
),
|
||||
"mobile_sync::start_mobile_sync" => json(crate::mobile_sync::start_mobile_sync(app)?),
|
||||
"mobile_sync::get_mobile_sync_status" => {
|
||||
json(crate::mobile_sync::get_mobile_sync_status(app)?)
|
||||
}
|
||||
"mobile_sync::rotate_mobile_pairing" => {
|
||||
json(crate::mobile_sync::rotate_mobile_pairing(app)?)
|
||||
}
|
||||
"mobile_sync::stop_mobile_sync" => json(crate::mobile_sync::stop_mobile_sync(app)?),
|
||||
"mobile_sync::revoke_mobile_sync_device" => json(
|
||||
crate::mobile_sync::revoke_mobile_sync_device(app, input(&payload)?)?,
|
||||
),
|
||||
"mobile_sync::get_mobile_sync_snapshot" => {
|
||||
json(crate::mobile_sync::get_mobile_sync_snapshot(app)?)
|
||||
}
|
||||
"native_composition::get_native_composition_module_registry" => {
|
||||
json(crate::native_composition::get_native_composition_module_registry())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').
|
|||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
const EducationWorkspace = lazy(() => import('./modules/education-workspace').then((module) => ({ default: module.EducationWorkspace })))
|
||||
const WebNovelWorkspace = lazy(() => import('./modules/web-novel/WebNovelWorkspace').then((module) => ({ default: module.WebNovelWorkspace })))
|
||||
const MobileSyncPanel = lazy(() => import('./modules/mobile-sync').then((module) => ({ default: module.MobileSyncPanel })))
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'education' | 'webNovel' | 'mobileSync' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
|
|
@ -369,7 +370,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: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', education: '教育工作台', webNovel: '网文作者工作台', mobileSync: '移动同步桥', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -615,6 +616,9 @@ function HoloLakeApp() {
|
|||
const [webNovelModule, setWebNovelModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [webNovelBusy, setWebNovelBusy] = useState(false)
|
||||
const [webNovelMessage, setWebNovelMessage] = useState('')
|
||||
const [mobileSyncModule, setMobileSyncModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [mobileSyncBusy, setMobileSyncBusy] = useState(false)
|
||||
const [mobileSyncMessage, setMobileSyncMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1207,6 +1211,27 @@ function HoloLakeApp() {
|
|||
finally { setWebNovelBusy(false) }
|
||||
}
|
||||
|
||||
const refreshMobileSyncModule = async () => {
|
||||
try {
|
||||
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
|
||||
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-OFFICIAL-MOBILE-SYNC-0001') || null
|
||||
setMobileSyncModule(module)
|
||||
return module
|
||||
} catch (error) { setMobileSyncMessage(humanError(error, 'system')); return null }
|
||||
}
|
||||
const openMobileSync = () => { openWorldTool('mobileSync'); void refreshMobileSyncModule() }
|
||||
const activateMobileSyncModule = async () => {
|
||||
setMobileSyncBusy(true)
|
||||
setMobileSyncMessage('正在验证官方签名、登记通信编号并确认五项本机网络边界……')
|
||||
try {
|
||||
await invoke('activate_bundled_module', { input: { moduleNumber: 'HLP-MOD-OFFICIAL-MOBILE-SYNC-0001', humanConfirmedPermissionExpansion: true } })
|
||||
const module = await refreshMobileSyncModule()
|
||||
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
|
||||
setMobileSyncMessage('移动同步桥已通过签名、编号、权限和自检验收;监听器仍保持关闭,等待本人显式开启。')
|
||||
} catch (error) { setMobileSyncMessage(humanError(error, 'system')) }
|
||||
finally { setMobileSyncBusy(false) }
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!cloneUrl.trim()) return
|
||||
|
|
@ -1635,6 +1660,20 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderMobileSync = () => (
|
||||
<section className="full-workbench mobile-sync-workbench-world">
|
||||
{mobileSyncModule?.installedState === 'ACTIVE'
|
||||
? <Suspense fallback={<div className="workbench-empty"><p>正在装载移动同步桥……</p></div>}><MobileSyncPanel onBack={() => setWorldStage(toolReturnStage)}/></Suspense>
|
||||
: <div className="workbench-empty">
|
||||
<span>桥</span><h2>启用移动同步桥</h2>
|
||||
<p>模块只在本人确认后开放同一局域网入口;电脑仍是唯一根节点,手机只读取最小投影并把快速记录写入隔离收件箱。</p>
|
||||
<code>HLP-MOD-OFFICIAL-MOBILE-SYNC-0001 · 5 项本机网络权限</code>
|
||||
<button className="primary-button" type="button" disabled={mobileSyncBusy} onClick={() => void activateMobileSyncModule()}>{mobileSyncBusy ? '正在验收官方模块…' : '确认五项边界并启用'}</button>
|
||||
{mobileSyncMessage && <p>{mobileSyncMessage}</p>}
|
||||
</div>}
|
||||
</section>
|
||||
)
|
||||
|
||||
const renderCode = () => (
|
||||
<section className="full-workbench code-workbench">
|
||||
<aside className="code-channels">
|
||||
|
|
@ -1906,6 +1945,7 @@ function HoloLakeApp() {
|
|||
<LakePool className="channel-code" title="资料工作台" meta="签名模块 · 文档与智能表格" onClick={openWorkbench}/>
|
||||
<LakePool className="channel-education" title="教育工作台" meta="官方编号模块 · 教育文档、表格与自动化" onClick={openEducation}/>
|
||||
<LakePool className="channel-main" title="网文作者工作台" meta="官方编号模块 · 码字、设定、编辑与交付" onClick={openWebNovel}/>
|
||||
<LakePool className="channel-mobile" title="移动同步桥" meta="同一人格系统 · 局域网加密入口" onClick={openMobileSync}/>
|
||||
{timeAuthorityModule && <LakePool className="channel-time" title="时间主控" meta={beijingCoordinate ? `光湖历第 ${beijingCoordinate.guanghuEraDay} 天` : '北京时间正在流动'} onClick={openEraTimeline}/>}
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</section>}
|
||||
|
|
@ -1919,7 +1959,7 @@ function HoloLakeApp() {
|
|||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'education' ? renderEducation() : view === 'webNovel' ? renderWebNovel() : view === 'mobileSync' ? renderMobileSync() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { numberedInvoke as invoke } from '../numbered-ipc'
|
||||
import './styles.css'
|
||||
|
||||
interface PairedDevice {
|
||||
deviceId: string
|
||||
displayName: string
|
||||
platform: string
|
||||
createdAtUnixMs: number
|
||||
lastSeenAtUnixMs?: number
|
||||
state: 'ACTIVE' | 'REVOKED'
|
||||
}
|
||||
|
||||
interface MobileSyncStatus {
|
||||
schema: string
|
||||
state: 'OFFLINE' | 'PAIRING_READY' | 'RUNNING'
|
||||
running: boolean
|
||||
lanAddress?: string
|
||||
port?: number
|
||||
pairingUri?: string
|
||||
pairingQrSvg?: string
|
||||
pairingExpiresAtUnixMs?: number
|
||||
pairedDevices: PairedDevice[]
|
||||
transport: string
|
||||
encryption: string
|
||||
desktopIsRootNode: boolean
|
||||
platformPrivateDataCustody: boolean
|
||||
}
|
||||
|
||||
interface MobileCapture {
|
||||
cursor: number
|
||||
captureId: string
|
||||
title: string
|
||||
body: string
|
||||
sourceDeviceId: string
|
||||
createdAtUnixMs: number
|
||||
}
|
||||
|
||||
interface MobileSyncSnapshot {
|
||||
state: string
|
||||
cursor: number
|
||||
generatedAtUnixMs: number
|
||||
desktopName: string
|
||||
rootNodeOnline: boolean
|
||||
personalChannel: { home: string; growthEventCount: number; integrity: string }
|
||||
webNovel: {
|
||||
workCount: number
|
||||
volumeCount: number
|
||||
chapterCount: number
|
||||
works: Array<{ workId: string; title: string; status: string; updatedAtUnixMs: number }>
|
||||
}
|
||||
education: {
|
||||
activeTableCount: number
|
||||
archivedTableCount: number
|
||||
unassignedTableCount: number
|
||||
sensitiveValuesIncluded: boolean
|
||||
}
|
||||
recentCaptures: MobileCapture[]
|
||||
boundary: {
|
||||
mobileRole: string
|
||||
remoteDesktopClone: boolean
|
||||
desktopOfflineExecution: boolean
|
||||
sensitiveEducationValues: string
|
||||
modelApi: string
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(value?: number) {
|
||||
return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '尚未连接'
|
||||
}
|
||||
|
||||
export function MobileSyncPanel({ onBack }: { onBack: () => void }) {
|
||||
const [status, setStatus] = useState<MobileSyncStatus | null>(null)
|
||||
const [snapshot, setSnapshot] = useState<MobileSyncSnapshot | null>(null)
|
||||
const [message, setMessage] = useState('手机是当前个人节点的远程入口,不是第二套人格系统。')
|
||||
const [pending, setPending] = useState(false)
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const next = await invoke<MobileSyncStatus>('get_mobile_sync_status')
|
||||
setStatus(next)
|
||||
if (next.running) setSnapshot(await invoke<MobileSyncSnapshot>('get_mobile_sync_snapshot'))
|
||||
else setSnapshot(null)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
refresh().catch((error) => !cancelled && setMessage(String(error)))
|
||||
return () => { cancelled = true }
|
||||
}, [refresh])
|
||||
|
||||
const run = async (action: () => Promise<unknown>, success: string) => {
|
||||
if (pending) return
|
||||
setPending(true)
|
||||
try {
|
||||
await action()
|
||||
setMessage(success)
|
||||
await refresh()
|
||||
} catch (error) {
|
||||
setMessage(String(error))
|
||||
} finally {
|
||||
setPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
const qrSource = useMemo(
|
||||
() => status?.pairingQrSvg ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(status.pairingQrSvg)}` : '',
|
||||
[status?.pairingQrSvg],
|
||||
)
|
||||
|
||||
return <section className="mobile-sync-world" aria-label="HoloLake 多端同步">
|
||||
<header className="mobile-sync-worldbar">
|
||||
<button type="button" onClick={onBack}>← 返回我的频道</button>
|
||||
<div><span>HOLOLAKE / SAME PERSONA SYSTEM</span><b>iPhone 多端同步</b></div>
|
||||
<em>{status?.running ? '个人主节点在线' : '同步未开启'}</em>
|
||||
</header>
|
||||
|
||||
<div className="mobile-sync-focus">
|
||||
<div className={`mobile-sync-orbit ${status?.running ? 'is-online' : ''}`} aria-hidden="true"><i/><i/><i/></div>
|
||||
<div><span>手机远程身体入口</span><h1>{status?.running ? '同一频道,正在等待 iPhone' : '由这台电脑守住唯一真相'}</h1><p>桌面保留身份、人格、数据与执行主权;手机只同步经过授权的投影与快速记录。</p></div>
|
||||
</div>
|
||||
|
||||
{!status?.running ? <section className="mobile-sync-start">
|
||||
<b>同一局域网 · 应用层端到端加密</b>
|
||||
<p>开启后只在当前登录账号的数据根建立同步库。平台服务器不托管你的作品、教育数据、人格记忆或设备密钥。</p>
|
||||
<button type="button" disabled={pending} onClick={() => void run(() => invoke('start_mobile_sync'), 'iPhone 同步入口已开启。')}>{pending ? '正在开启…' : '开启 iPhone 同步'}</button>
|
||||
</section> : <div className="mobile-sync-grid">
|
||||
<section className="mobile-sync-pairing">
|
||||
<header><div><span>PAIRING</span><h2>配对这台 iPhone</h2></div><button type="button" disabled={pending} onClick={() => void run(() => invoke('rotate_mobile_pairing'), '已换成新的十分钟配对凭据。')}>换一枚配对凭据</button></header>
|
||||
{status.pairingUri ? <>
|
||||
{qrSource && <img src={qrSource} alt="HoloLake iPhone 配对二维码"/>}
|
||||
<p>用 iPhone 版 HoloLake 扫描。二维码内含一次性高强度配对密钥,十分钟后或使用一次后失效。</p>
|
||||
<button type="button" onClick={() => void navigator.clipboard.writeText(status.pairingUri || '').then(() => setMessage('配对链接已复制。'))}>复制配对链接</button>
|
||||
<small>到期:{formatTime(status.pairingExpiresAtUnixMs)}</small>
|
||||
</> : <div className="mobile-sync-pairing-used"><b>当前配对凭据已使用或到期</b><p>需要添加另一台手机时,再生成一枚新的凭据。</p></div>}
|
||||
</section>
|
||||
|
||||
<section className="mobile-sync-projection">
|
||||
<header><span>ROOT NODE PROJECTION</span><h2>当前同步投影</h2></header>
|
||||
<div className="mobile-sync-metrics">
|
||||
<article><b>{snapshot?.webNovel.workCount ?? 0}</b><span>网文作品</span></article>
|
||||
<article><b>{snapshot?.webNovel.chapterCount ?? 0}</b><span>章节</span></article>
|
||||
<article><b>{snapshot?.education.activeTableCount ?? 0}</b><span>教育表格</span></article>
|
||||
<article><b>{snapshot?.personalChannel.growthEventCount ?? 0}</b><span>成长事件</span></article>
|
||||
</div>
|
||||
<p>教育敏感值:{snapshot?.education.sensitiveValuesIncluded ? '已包含' : '不会进入手机摘要'} · 电脑离线时手机不会冒充执行。</p>
|
||||
</section>
|
||||
|
||||
<section className="mobile-sync-devices">
|
||||
<header><span>DEVICES</span><h2>已配对设备</h2></header>
|
||||
{status.pairedDevices.length ? status.pairedDevices.map((device) => <article key={device.deviceId}>
|
||||
<div><b>{device.displayName}</b><span>{device.platform} · {device.state === 'ACTIVE' ? `最近连接 ${formatTime(device.lastSeenAtUnixMs)}` : '已撤销'}</span></div>
|
||||
{device.state === 'ACTIVE' && <button type="button" disabled={pending} onClick={() => void run(() => invoke('revoke_mobile_sync_device', { input: { deviceId: device.deviceId } }), `已撤销 ${device.displayName}。`)}>撤销</button>}
|
||||
</article>) : <p>还没有完成配对的 iPhone。</p>}
|
||||
</section>
|
||||
|
||||
<section className="mobile-sync-captures">
|
||||
<header><span>MOBILE CAPTURES</span><h2>手机快速记录</h2></header>
|
||||
{snapshot?.recentCaptures.length ? snapshot.recentCaptures.map((capture) => <article key={capture.captureId}><div><b>{capture.title || '未命名记录'}</b><time>{formatTime(capture.createdAtUnixMs)}</time></div><p>{capture.body}</p></article>) : <p>手机写下的想法会加密回到这台电脑,并出现在这里。</p>}
|
||||
</section>
|
||||
</div>}
|
||||
|
||||
<footer className="mobile-sync-footer"><span>{message}</span><div><button type="button" disabled={pending} onClick={() => void run(refresh, '状态已从本机根节点重新读取。')}>刷新状态</button>{status?.running && <button type="button" disabled={pending} onClick={() => void run(() => invoke('stop_mobile_sync'), '同步入口已关闭;已配对设备登记仍保留,可单独撤销。')}>关闭本机同步入口</button>}</div></footer>
|
||||
</section>
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1120,6 +1120,54 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0029",
|
||||
"operationNumber": "HLP-NIPC-OP-0140",
|
||||
"targetNumber": "HLP-NIPC-TGT-0029"
|
||||
},
|
||||
"start_mobile_sync": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0141",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
},
|
||||
"get_mobile_sync_status": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0142",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
},
|
||||
"rotate_mobile_pairing": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0143",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
},
|
||||
"stop_mobile_sync": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0144",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
},
|
||||
"revoke_mobile_sync_device": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0145",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
},
|
||||
"get_mobile_sync_snapshot": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0030",
|
||||
"operationNumber": "HLP-NIPC-OP-0146",
|
||||
"targetNumber": "HLP-NIPC-TGT-0030"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -681,6 +681,7 @@ svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-lineca
|
|||
.channel-main .pool-bay { width: 260px; height: 84px; } .channel-main .pool-label { top: 92px; }
|
||||
.channel-knowledge { left: 14%; top: 48%; } .channel-code { left: 31%; top: 61%; } .channel-light { right: 31%; top: 61%; } .channel-status { right: 14%; top: 48%; }
|
||||
.channel-time { left: 50%; bottom: 4%; transform: translateX(-50%); }
|
||||
.channel-mobile { right: 7%; top: 70%; }
|
||||
.private-route-note { position: absolute; z-index: 10; left: 50%; bottom: 11%; width: min(720px, calc(100% - 64px)); margin: 0; transform: translateX(-50%); color: var(--content-muted); text-align: center; font-size: 12.5px; font-weight: 650; letter-spacing: .08em; }
|
||||
.personal-node-guide { position: absolute; z-index: 12; left: 50%; top: 55%; width: min(760px, calc(100% - 72px)); max-height: calc(100% - 190px); overflow: auto; padding: 26px 30px; transform: translate(-50%, -50%); border: 1px solid var(--panel-edge); border-radius: 20px; color: var(--content-secondary); background: color-mix(in srgb, var(--panel-bg) 88%, transparent); box-shadow: 0 30px 90px rgba(0, 0, 0, .42); backdrop-filter: blur(22px); }
|
||||
.personal-node-guide header span { color: var(--accent-light); font-size: 11px; font-weight: 750; letter-spacing: .2em; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue