foundation: start native language reality engineering root
This commit is contained in:
commit
0dec0b93fc
154 changed files with 49993 additions and 0 deletions
8
parts/HLP-PART-0008/PART.hldp
Normal file
8
parts/HLP-PART-0008/PART.hldp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
schema: hololake.numbered-part/v1
|
||||
id: HLP-PART-0008
|
||||
display_name: 签名模块生命周期核
|
||||
state: DONOR_BYTES_EXTRACTED
|
||||
source_commit: 2e48b7d7686554454b6ee69bcdbd01eb68bff401
|
||||
payload_sha256: c69d8fc613252e61e0cb909e07afa7090d7a33d08034586ca2760314aaf46cd8
|
||||
payload_files: 6
|
||||
native_acceptance: PENDING
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"schema": "hololake.module-package-runtime/v1",
|
||||
"record_id": "HLP-MODULE-PACKAGE-RUNTIME-001",
|
||||
"protocols": ["GLS-0710", "GLS-0803", "GLS-0819", "GLS-0310"],
|
||||
"package_schema": "hololake.module-package/v1",
|
||||
"signature": {
|
||||
"algorithm": "MINISIGN_ED25519",
|
||||
"trust_source": "src-tauri/release-trust.json",
|
||||
"signature_is_detached": true,
|
||||
"package_bytes_are_signed_exactly": true,
|
||||
"source_repository_is_executable": false
|
||||
},
|
||||
"number_classes": {
|
||||
"official": "HLP-MOD-OFFICIAL-*",
|
||||
"private_channel": "HLP-MOD-LOCAL-*",
|
||||
"candidate_number_is_runtime_number": false
|
||||
},
|
||||
"host": {
|
||||
"version": "0.5.0",
|
||||
"maximum_package_bytes": 16777216,
|
||||
"arbitrary_native_code": false,
|
||||
"arbitrary_webview_javascript": false,
|
||||
"declarative_payload_only": true
|
||||
},
|
||||
"registered_adapters": [
|
||||
"native-composition-v1",
|
||||
"channel-workbench-v1",
|
||||
"persona-channel-body-v1",
|
||||
"education-workbench-v1",
|
||||
"web-novel-workbench-v1",
|
||||
"mobile-sync-v1",
|
||||
"dynamic-language-world-surface-v1"
|
||||
],
|
||||
"lifecycle": [
|
||||
"INSTALLED_DORMANT",
|
||||
"MOUNTED_PENDING_SELF_TEST",
|
||||
"ACTIVE",
|
||||
"DORMANT",
|
||||
"ROLLBACK_PENDING_SELF_TEST",
|
||||
"FAILED_CLOSED"
|
||||
],
|
||||
"rules": {
|
||||
"verified_human_route_required": true,
|
||||
"permission_expansion_requires_human_confirmation": true,
|
||||
"mount_never_executes_package_code": true,
|
||||
"self_test_before_active": true,
|
||||
"failed_self_test_rolls_back": true,
|
||||
"restart_reconstructs_from_sqlite": true,
|
||||
"unmount_preserves_user_data": true,
|
||||
"every_mutation_writes_hash_chained_receipt": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"schema": "hololake.native-composition-runtime/v1",
|
||||
"record_id": "HLP-NATIVE-COMPOSITION-001",
|
||||
"candidate_number": "HLP-DONOR-CAND-0001",
|
||||
"runtime_module_number": "HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001",
|
||||
"state": "ADMITTED_INSTALLED_RUNTIME_ACCEPTED",
|
||||
"provenance": {
|
||||
"donor": "0.4.1 read-only candidate paths",
|
||||
"donor_contract_state": "INSTALLED_RUNTIME_ACCEPTED",
|
||||
"donor_apple_notarization": "NOT_PERFORMED_MISSING_CREDENTIALS",
|
||||
"current_mainline_reuses_bulk_source": false
|
||||
},
|
||||
"internal_object": {
|
||||
"schema": "hololake.native-object/v1",
|
||||
"encoding": "BOUNDED_JSON",
|
||||
"script_allowed": false,
|
||||
"html_allowed": false,
|
||||
"external_file_format_as_runtime_allowed": false,
|
||||
"maximum_rows": 5000,
|
||||
"maximum_columns": 64,
|
||||
"maximum_cell_bytes": 10000
|
||||
},
|
||||
"module_contract": {
|
||||
"schema": "hololake.composition-module/v1",
|
||||
"registered_kinds": ["SOURCE", "TRANSFORM", "PROJECTION"],
|
||||
"typed_ports_required": true,
|
||||
"determinism_declared": true,
|
||||
"authority": "CURRENT_AUTHENTICATED_ACCOUNT_READ_ONLY"
|
||||
},
|
||||
"recipe": {
|
||||
"schema": "hololake.composition-recipe/v1",
|
||||
"registered_modules_only": true,
|
||||
"directed_acyclic_graph_required": true,
|
||||
"current_account_only": true,
|
||||
"native_validation_required": true
|
||||
},
|
||||
"human_projection": {
|
||||
"views": ["DASHBOARD", "COMPARISON", "VERTICAL_BAR", "CLASSIFICATION", "TABLE"],
|
||||
"shares_one_execution_result": true,
|
||||
"owns_source_data": false,
|
||||
"direct_write_authority": false
|
||||
},
|
||||
"admission": {
|
||||
"signed_package_required": true,
|
||||
"adapter": "native-composition-v1",
|
||||
"numbered_routes_required": true,
|
||||
"active_module_required_before_execution": true,
|
||||
"installed_runtime_acceptance_required": true,
|
||||
"hardcoded_sample_may_satisfy_acceptance": false
|
||||
},
|
||||
"current_acceptance": {
|
||||
"observed_at": "2026-08-19T01:49:35+08:00",
|
||||
"signed_debug_binary_sha256": "0bff08787950b69ef0a1565fc32a7537dae53da184c4a1a4fb30ca62eca9f48c",
|
||||
"developer_id_team": "825A9L3G7Q",
|
||||
"module_package_sha256": "dfb343019810f8f845ab415259be2eccbe63d88a03bf26a277950610edb64329",
|
||||
"account_number_observed": "ICE-GL∞",
|
||||
"real_native_rows": 129,
|
||||
"real_native_total_bytes_display": "1.0 MB",
|
||||
"real_groups": 14,
|
||||
"measure_switch_observed": "DOCUMENT_COUNT_TO_TOTAL_BYTES",
|
||||
"install_receipts": ["INSTALL", "MOUNT", "SELF_TEST_PASS"],
|
||||
"restart_recovery": "ACTIVE_AND_REEXECUTED",
|
||||
"execution_receipt_prefixes": ["0449582510a5", "092febd75c38"],
|
||||
"apple_notarization": "FINAL_RELEASE_PENDING_ALL_MODULES"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const contract = JSON.parse(readFileSync(new URL('../contracts/module-package-runtime.json', import.meta.url), 'utf8'))
|
||||
const registry = JSON.parse(readFileSync(new URL('../contracts/numbered-ipc-registry.json', import.meta.url), 'utf8'))
|
||||
const rust = readFileSync(new URL('../src-tauri/src/module_package_runtime.rs', import.meta.url), 'utf8')
|
||||
|
||||
test('module packages are signed declarative artifacts rather than executable repositories', () => {
|
||||
assert.equal(contract.schema, 'hololake.module-package-runtime/v1')
|
||||
assert.equal(contract.signature.source_repository_is_executable, false)
|
||||
assert.equal(contract.signature.package_bytes_are_signed_exactly, true)
|
||||
assert.equal(contract.host.arbitrary_native_code, false)
|
||||
assert.equal(contract.host.arbitrary_webview_javascript, false)
|
||||
assert.equal(contract.host.declarative_payload_only, true)
|
||||
assert.equal(contract.rules.self_test_before_active, true)
|
||||
assert.equal(contract.rules.failed_self_test_rolls_back, true)
|
||||
assert.equal(contract.rules.unmount_preserves_user_data, true)
|
||||
})
|
||||
|
||||
test('module lifecycle is reachable only through numbered verified-human routes', () => {
|
||||
const routes = registry.operations.filter((route) => route.module_number === 'HLP-NIPC-MOD-0020')
|
||||
assert.equal(routes.length, 9)
|
||||
assert.deepEqual(routes.map((route) => route.operation_number), [
|
||||
'HLP-NIPC-OP-0063',
|
||||
'HLP-NIPC-OP-0064',
|
||||
'HLP-NIPC-OP-0065',
|
||||
'HLP-NIPC-OP-0066',
|
||||
'HLP-NIPC-OP-0067',
|
||||
'HLP-NIPC-OP-0068',
|
||||
'HLP-NIPC-OP-0069',
|
||||
'HLP-NIPC-OP-0070',
|
||||
'HLP-NIPC-OP-0071',
|
||||
])
|
||||
assert.ok(routes.every((route) => route.admission === 'VERIFIED_HUMAN_ROUTE'))
|
||||
assert.match(rust, /MINISIGN_ED25519|PublicKey::decode/)
|
||||
assert.match(rust, /module_receipts/)
|
||||
assert.match(rust, /ROLLBACK_PENDING_SELF_TEST/)
|
||||
})
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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/native-composition-runtime.json'))
|
||||
const registry = JSON.parse(read('contracts/numbered-ipc-registry.json'))
|
||||
const modulePackage = JSON.parse(read('fixtures/module-packages/HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001-0.1.0.ghmod'))
|
||||
const rust = read('src-tauri/src/native_composition.rs')
|
||||
const frontend = read('src/modules/native-composition/index.tsx')
|
||||
|
||||
test('native composition is an admitted numbered read-only adapter, not donor runtime code', () => {
|
||||
assert.equal(contract.candidate_number, 'HLP-DONOR-CAND-0001')
|
||||
assert.equal(contract.runtime_module_number, 'HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001')
|
||||
assert.equal(contract.module_contract.authority, 'CURRENT_AUTHENTICATED_ACCOUNT_READ_ONLY')
|
||||
assert.equal(contract.admission.signed_package_required, true)
|
||||
assert.equal(contract.admission.active_module_required_before_execution, true)
|
||||
assert.equal(contract.provenance.current_mainline_reuses_bulk_source, false)
|
||||
assert.equal(modulePackage.manifest.adapter, 'native-composition-v1')
|
||||
assert.deepEqual(modulePackage.manifest.permissions, ['KNOWLEDGE_READ'])
|
||||
assert.match(rust, /require_active_module_adapter/)
|
||||
assert.doesNotMatch(rust, /#\[tauri::command\]/)
|
||||
assert.match(frontend, /NativeCompositionStudio/)
|
||||
})
|
||||
|
||||
test('native composition execution has one exact numbered coordinate pair', () => {
|
||||
const routes = registry.operations.filter((route) => route.module_number === 'HLP-NIPC-MOD-0021')
|
||||
assert.deepEqual(routes.map((route) => route.operation_number), ['HLP-NIPC-OP-0072', 'HLP-NIPC-OP-0073'])
|
||||
assert.ok(routes.every((route) => route.target_number === 'HLP-NIPC-TGT-0021'))
|
||||
assert.ok(routes.every((route) => route.admission === 'VERIFIED_HUMAN_ROUTE'))
|
||||
})
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,839 @@
|
|||
//! HoloLake 原生组合执行内核。
|
||||
//!
|
||||
//! 外来文件格式不进入这里。内核只接受登记过的模块、受限配方和当前登录账号的
|
||||
//! HoloLake 原生对象;人类投影只是同一执行结果的不同视图,没有源数据所有权。
|
||||
|
||||
use ring::digest::{digest, SHA256};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use tauri::AppHandle;
|
||||
use uuid::Uuid;
|
||||
|
||||
const REGISTRY_SCHEMA: &str = "hololake.composition-module-registry/v1";
|
||||
const OBJECT_SCHEMA: &str = "hololake.native-object/v1";
|
||||
const RECIPE_SCHEMA: &str = "hololake.composition-recipe/v1";
|
||||
const PROJECTION_SCHEMA: &str = "hololake.human-projection/v1";
|
||||
const MAX_ROWS: usize = 5_000;
|
||||
const MAX_VIEWS: usize = 5;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum CompositionDimension {
|
||||
Source,
|
||||
TopLevelFolder,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum CompositionMeasure {
|
||||
DocumentCount,
|
||||
TotalBytes,
|
||||
DuplicateCount,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum ProjectionView {
|
||||
Dashboard,
|
||||
Comparison,
|
||||
VerticalBar,
|
||||
Classification,
|
||||
Table,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ExecuteCompositionInput {
|
||||
pub dimension: CompositionDimension,
|
||||
pub measure: CompositionMeasure,
|
||||
pub views: Vec<ProjectionView>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleDescriptor {
|
||||
pub module_id: &'static str,
|
||||
pub display_name: &'static str,
|
||||
pub kind: &'static str,
|
||||
pub input_schema: Option<&'static str>,
|
||||
pub output_schema: &'static str,
|
||||
pub deterministic: bool,
|
||||
pub authority: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleRegistry {
|
||||
pub schema: &'static str,
|
||||
pub state: &'static str,
|
||||
pub modules: Vec<ModuleDescriptor>,
|
||||
pub arbitrary_script_allowed: bool,
|
||||
pub unregistered_module_allowed: bool,
|
||||
pub direct_projection_write_allowed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeColumn {
|
||||
pub column_id: &'static str,
|
||||
pub title: &'static str,
|
||||
pub value_type: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeKnowledgeRow {
|
||||
pub row_id: String,
|
||||
pub source: String,
|
||||
pub path: String,
|
||||
pub title: String,
|
||||
pub top_level_folder: String,
|
||||
pub size_bytes: u64,
|
||||
pub duplicate_count: usize,
|
||||
pub updated_at_unix_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeObject {
|
||||
pub schema: &'static str,
|
||||
pub object_id: String,
|
||||
pub title: &'static str,
|
||||
pub columns: Vec<NativeColumn>,
|
||||
pub rows: Vec<NativeKnowledgeRow>,
|
||||
pub row_count: usize,
|
||||
pub truncated: bool,
|
||||
pub source_receipt: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompositionNode {
|
||||
pub node_id: String,
|
||||
pub module_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompositionEdge {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompositionRecipe {
|
||||
pub schema: &'static str,
|
||||
pub recipe_id: &'static str,
|
||||
pub title: &'static str,
|
||||
pub nodes: Vec<CompositionNode>,
|
||||
pub edges: Vec<CompositionEdge>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProjectionGroup {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub document_count: usize,
|
||||
pub total_bytes: u64,
|
||||
pub duplicate_count: usize,
|
||||
pub measure_value: u64,
|
||||
pub share: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProjectionMetrics {
|
||||
pub raw_document_count: usize,
|
||||
pub unique_document_count: usize,
|
||||
pub duplicate_document_count: usize,
|
||||
pub total_bytes: u64,
|
||||
pub group_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NativeCompositionProjection {
|
||||
pub schema: &'static str,
|
||||
pub state: &'static str,
|
||||
pub execution_id: String,
|
||||
pub executed_at_unix_ms: u64,
|
||||
pub source_is_real_account_data: bool,
|
||||
pub read_only: bool,
|
||||
pub dimension: CompositionDimension,
|
||||
pub measure: CompositionMeasure,
|
||||
pub views: Vec<ProjectionView>,
|
||||
pub native_object: NativeObject,
|
||||
pub groups: Vec<ProjectionGroup>,
|
||||
pub metrics: ProjectionMetrics,
|
||||
pub recipe: CompositionRecipe,
|
||||
pub data_sha256: String,
|
||||
pub receipt_sha256: String,
|
||||
}
|
||||
|
||||
fn registry() -> ModuleRegistry {
|
||||
let modules = vec![
|
||||
descriptor(
|
||||
"HLC-SOURCE-KNOWLEDGE-CATALOG",
|
||||
"知识目录数据源",
|
||||
"SOURCE",
|
||||
None,
|
||||
OBJECT_SCHEMA,
|
||||
),
|
||||
descriptor(
|
||||
"HLC-CLASSIFY-SOURCE",
|
||||
"按来源分类",
|
||||
"TRANSFORM",
|
||||
Some(OBJECT_SCHEMA),
|
||||
"hololake.classified-object/v1",
|
||||
),
|
||||
descriptor(
|
||||
"HLC-CLASSIFY-TOP-FOLDER",
|
||||
"按一级目录分类",
|
||||
"TRANSFORM",
|
||||
Some(OBJECT_SCHEMA),
|
||||
"hololake.classified-object/v1",
|
||||
),
|
||||
descriptor(
|
||||
"HLC-AGGREGATE-DOCUMENT-COUNT",
|
||||
"统计文档数",
|
||||
"TRANSFORM",
|
||||
Some("hololake.classified-object/v1"),
|
||||
"hololake.composition-result/v1",
|
||||
),
|
||||
descriptor(
|
||||
"HLC-AGGREGATE-TOTAL-BYTES",
|
||||
"统计数据量",
|
||||
"TRANSFORM",
|
||||
Some("hololake.classified-object/v1"),
|
||||
"hololake.composition-result/v1",
|
||||
),
|
||||
descriptor(
|
||||
"HLC-AGGREGATE-DUPLICATE-COUNT",
|
||||
"统计重复数",
|
||||
"TRANSFORM",
|
||||
Some("hololake.classified-object/v1"),
|
||||
"hololake.composition-result/v1",
|
||||
),
|
||||
descriptor(
|
||||
"HLC-PROJECT-DASHBOARD",
|
||||
"仪表盘投影",
|
||||
"PROJECTION",
|
||||
Some("hololake.composition-result/v1"),
|
||||
PROJECTION_SCHEMA,
|
||||
),
|
||||
descriptor(
|
||||
"HLC-PROJECT-COMPARISON",
|
||||
"对比投影",
|
||||
"PROJECTION",
|
||||
Some("hololake.composition-result/v1"),
|
||||
PROJECTION_SCHEMA,
|
||||
),
|
||||
descriptor(
|
||||
"HLC-PROJECT-VERTICAL-BAR",
|
||||
"柱状图投影",
|
||||
"PROJECTION",
|
||||
Some("hololake.composition-result/v1"),
|
||||
PROJECTION_SCHEMA,
|
||||
),
|
||||
descriptor(
|
||||
"HLC-PROJECT-CLASSIFICATION",
|
||||
"分类投影",
|
||||
"PROJECTION",
|
||||
Some("hololake.composition-result/v1"),
|
||||
PROJECTION_SCHEMA,
|
||||
),
|
||||
descriptor(
|
||||
"HLC-PROJECT-TABLE",
|
||||
"表格投影",
|
||||
"PROJECTION",
|
||||
Some("hololake.composition-result/v1"),
|
||||
PROJECTION_SCHEMA,
|
||||
),
|
||||
];
|
||||
ModuleRegistry {
|
||||
schema: REGISTRY_SCHEMA,
|
||||
state: "READY",
|
||||
modules,
|
||||
arbitrary_script_allowed: false,
|
||||
unregistered_module_allowed: false,
|
||||
direct_projection_write_allowed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn descriptor(
|
||||
module_id: &'static str,
|
||||
display_name: &'static str,
|
||||
kind: &'static str,
|
||||
input_schema: Option<&'static str>,
|
||||
output_schema: &'static str,
|
||||
) -> ModuleDescriptor {
|
||||
ModuleDescriptor {
|
||||
module_id,
|
||||
display_name,
|
||||
kind,
|
||||
input_schema,
|
||||
output_schema,
|
||||
deterministic: true,
|
||||
authority: "CURRENT_AUTHENTICATED_ACCOUNT_READ_ONLY",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_native_composition_module_registry() -> ModuleRegistry {
|
||||
registry()
|
||||
}
|
||||
|
||||
pub async fn execute_knowledge_native_composition(
|
||||
app: AppHandle,
|
||||
input: ExecuteCompositionInput,
|
||||
) -> Result<NativeCompositionProjection, String> {
|
||||
crate::module_package_runtime::require_active_module_adapter(
|
||||
&app,
|
||||
"HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001",
|
||||
"native-composition-v1",
|
||||
)?;
|
||||
validate_input(&input)?;
|
||||
let snapshot = crate::knowledge_base::get_knowledge_snapshot(app).await?;
|
||||
execute_snapshot(snapshot, input)
|
||||
}
|
||||
|
||||
fn validate_input(input: &ExecuteCompositionInput) -> Result<(), String> {
|
||||
if input.views.is_empty() || input.views.len() > MAX_VIEWS {
|
||||
return Err("HOLOLAKE_COMPOSITION_VIEW_COUNT_INVALID".into());
|
||||
}
|
||||
let unique = input.views.iter().copied().collect::<BTreeSet<_>>();
|
||||
if unique.len() != input.views.len() {
|
||||
return Err("HOLOLAKE_COMPOSITION_DUPLICATE_VIEW".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_snapshot(
|
||||
snapshot: crate::knowledge_base::KnowledgeSnapshot,
|
||||
input: ExecuteCompositionInput,
|
||||
) -> Result<NativeCompositionProjection, String> {
|
||||
if snapshot.documents.len() > MAX_ROWS {
|
||||
return Err("HOLOLAKE_COMPOSITION_ROW_LIMIT_EXCEEDED".into());
|
||||
}
|
||||
let rows = snapshot
|
||||
.documents
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, document)| NativeKnowledgeRow {
|
||||
row_id: format!("knowledge-row-{}", index + 1),
|
||||
source: document.source.to_string(),
|
||||
path: document.path.clone(),
|
||||
title: document.title.clone(),
|
||||
top_level_folder: top_level_folder(&document.path),
|
||||
size_bytes: document.size_bytes,
|
||||
duplicate_count: document.duplicate_count,
|
||||
updated_at_unix_ms: document.updated_at_unix_ms.min(u64::MAX as u128) as u64,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let data_sha256 = sha256_json(&rows)?;
|
||||
let native_object = NativeObject {
|
||||
schema: OBJECT_SCHEMA,
|
||||
object_id: format!("knowledge-catalog-{}", &data_sha256[..16]),
|
||||
title: "当前账号知识目录",
|
||||
columns: native_columns(),
|
||||
row_count: rows.len(),
|
||||
rows,
|
||||
truncated: snapshot.truncated,
|
||||
source_receipt: "CURRENT_AUTHENTICATED_ACCOUNT_KNOWLEDGE_SNAPSHOT",
|
||||
};
|
||||
let mut aggregates = BTreeMap::<String, (usize, u64, usize)>::new();
|
||||
for row in &native_object.rows {
|
||||
let key = match input.dimension {
|
||||
CompositionDimension::Source => source_label(&row.source),
|
||||
CompositionDimension::TopLevelFolder => row.top_level_folder.clone(),
|
||||
};
|
||||
let entry = aggregates.entry(key).or_default();
|
||||
entry.0 += 1;
|
||||
entry.1 = entry.1.saturating_add(row.size_bytes);
|
||||
entry.2 = entry.2.saturating_add(row.duplicate_count);
|
||||
}
|
||||
let total_measure = aggregates
|
||||
.values()
|
||||
.map(|value| measure_value(*value, input.measure))
|
||||
.sum::<u64>();
|
||||
let mut groups = aggregates
|
||||
.into_iter()
|
||||
.map(|(label, value)| ProjectionGroup {
|
||||
key: stable_key(&label),
|
||||
label,
|
||||
document_count: value.0,
|
||||
total_bytes: value.1,
|
||||
duplicate_count: value.2,
|
||||
measure_value: measure_value(value, input.measure),
|
||||
share: if total_measure == 0 {
|
||||
0.0
|
||||
} else {
|
||||
measure_value(value, input.measure) as f64 / total_measure as f64
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
groups.sort_by(|left, right| {
|
||||
right
|
||||
.measure_value
|
||||
.cmp(&left.measure_value)
|
||||
.then_with(|| left.label.cmp(&right.label))
|
||||
});
|
||||
let recipe = compile_recipe(&input);
|
||||
validate_recipe(&recipe, ®istry())?;
|
||||
let metrics = ProjectionMetrics {
|
||||
raw_document_count: snapshot.raw_document_count,
|
||||
unique_document_count: snapshot.unique_document_count,
|
||||
duplicate_document_count: snapshot.duplicate_document_count,
|
||||
total_bytes: native_object.rows.iter().map(|row| row.size_bytes).sum(),
|
||||
group_count: groups.len(),
|
||||
};
|
||||
let executed_at_unix_ms = now_unix_ms();
|
||||
let receipt_material = serde_json::to_vec(&(
|
||||
&data_sha256,
|
||||
&recipe.nodes,
|
||||
&recipe.edges,
|
||||
&groups,
|
||||
executed_at_unix_ms,
|
||||
))
|
||||
.map_err(|error| format!("HOLOLAKE_COMPOSITION_RECEIPT_SERIALIZE_FAILED: {error}"))?;
|
||||
Ok(NativeCompositionProjection {
|
||||
schema: PROJECTION_SCHEMA,
|
||||
state: "EXECUTED",
|
||||
execution_id: format!("HLC-EXEC-{}", Uuid::new_v4()),
|
||||
executed_at_unix_ms,
|
||||
source_is_real_account_data: true,
|
||||
read_only: true,
|
||||
dimension: input.dimension,
|
||||
measure: input.measure,
|
||||
views: input.views,
|
||||
native_object,
|
||||
groups,
|
||||
metrics,
|
||||
recipe,
|
||||
data_sha256,
|
||||
receipt_sha256: sha256(&receipt_material),
|
||||
})
|
||||
}
|
||||
|
||||
fn native_columns() -> Vec<NativeColumn> {
|
||||
vec![
|
||||
NativeColumn {
|
||||
column_id: "title",
|
||||
title: "标题",
|
||||
value_type: "TEXT",
|
||||
},
|
||||
NativeColumn {
|
||||
column_id: "source",
|
||||
title: "来源",
|
||||
value_type: "TEXT",
|
||||
},
|
||||
NativeColumn {
|
||||
column_id: "topLevelFolder",
|
||||
title: "一级分类",
|
||||
value_type: "TEXT",
|
||||
},
|
||||
NativeColumn {
|
||||
column_id: "sizeBytes",
|
||||
title: "数据量",
|
||||
value_type: "INTEGER",
|
||||
},
|
||||
NativeColumn {
|
||||
column_id: "duplicateCount",
|
||||
title: "重复数",
|
||||
value_type: "INTEGER",
|
||||
},
|
||||
NativeColumn {
|
||||
column_id: "updatedAtUnixMs",
|
||||
title: "更新时间",
|
||||
value_type: "TIME",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn compile_recipe(input: &ExecuteCompositionInput) -> CompositionRecipe {
|
||||
let classify = match input.dimension {
|
||||
CompositionDimension::Source => "HLC-CLASSIFY-SOURCE",
|
||||
CompositionDimension::TopLevelFolder => "HLC-CLASSIFY-TOP-FOLDER",
|
||||
};
|
||||
let aggregate = match input.measure {
|
||||
CompositionMeasure::DocumentCount => "HLC-AGGREGATE-DOCUMENT-COUNT",
|
||||
CompositionMeasure::TotalBytes => "HLC-AGGREGATE-TOTAL-BYTES",
|
||||
CompositionMeasure::DuplicateCount => "HLC-AGGREGATE-DUPLICATE-COUNT",
|
||||
};
|
||||
let mut nodes = vec![
|
||||
CompositionNode {
|
||||
node_id: "source".into(),
|
||||
module_id: "HLC-SOURCE-KNOWLEDGE-CATALOG".into(),
|
||||
},
|
||||
CompositionNode {
|
||||
node_id: "classify".into(),
|
||||
module_id: classify.into(),
|
||||
},
|
||||
CompositionNode {
|
||||
node_id: "aggregate".into(),
|
||||
module_id: aggregate.into(),
|
||||
},
|
||||
];
|
||||
let mut edges = vec![
|
||||
CompositionEdge {
|
||||
from: "source".into(),
|
||||
to: "classify".into(),
|
||||
},
|
||||
CompositionEdge {
|
||||
from: "classify".into(),
|
||||
to: "aggregate".into(),
|
||||
},
|
||||
];
|
||||
for (index, view) in input.views.iter().enumerate() {
|
||||
let node_id = format!("projection-{}", index + 1);
|
||||
nodes.push(CompositionNode {
|
||||
node_id: node_id.clone(),
|
||||
module_id: view_module_id(*view).into(),
|
||||
});
|
||||
edges.push(CompositionEdge {
|
||||
from: "aggregate".into(),
|
||||
to: node_id,
|
||||
});
|
||||
}
|
||||
CompositionRecipe {
|
||||
schema: RECIPE_SCHEMA,
|
||||
recipe_id: "HLC-RECIPE-KNOWLEDGE-OVERVIEW-001",
|
||||
title: "知识空间结构组合",
|
||||
nodes,
|
||||
edges,
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_recipe(
|
||||
recipe: &CompositionRecipe,
|
||||
module_registry: &ModuleRegistry,
|
||||
) -> Result<(), String> {
|
||||
let descriptors = module_registry
|
||||
.modules
|
||||
.iter()
|
||||
.map(|item| (item.module_id, item))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let nodes = recipe
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|item| (item.node_id.as_str(), item))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
if nodes.len() != recipe.nodes.len() {
|
||||
return Err("HOLOLAKE_COMPOSITION_DUPLICATE_NODE".into());
|
||||
}
|
||||
for node in &recipe.nodes {
|
||||
if !descriptors.contains_key(node.module_id.as_str()) {
|
||||
return Err("HOLOLAKE_COMPOSITION_MODULE_NOT_REGISTERED".into());
|
||||
}
|
||||
}
|
||||
let mut indegree = recipe
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|item| (item.node_id.as_str(), 0usize))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let mut outgoing = BTreeMap::<&str, Vec<&str>>::new();
|
||||
for edge in &recipe.edges {
|
||||
let from = nodes
|
||||
.get(edge.from.as_str())
|
||||
.ok_or("HOLOLAKE_COMPOSITION_EDGE_NODE_UNKNOWN")?;
|
||||
let to = nodes
|
||||
.get(edge.to.as_str())
|
||||
.ok_or("HOLOLAKE_COMPOSITION_EDGE_NODE_UNKNOWN")?;
|
||||
let from_descriptor = descriptors
|
||||
.get(from.module_id.as_str())
|
||||
.ok_or("HOLOLAKE_COMPOSITION_MODULE_NOT_REGISTERED")?;
|
||||
let to_descriptor = descriptors
|
||||
.get(to.module_id.as_str())
|
||||
.ok_or("HOLOLAKE_COMPOSITION_MODULE_NOT_REGISTERED")?;
|
||||
if to_descriptor.input_schema != Some(from_descriptor.output_schema) {
|
||||
return Err("HOLOLAKE_COMPOSITION_PORT_TYPE_MISMATCH".into());
|
||||
}
|
||||
*indegree
|
||||
.get_mut(to.node_id.as_str())
|
||||
.ok_or("HOLOLAKE_COMPOSITION_EDGE_NODE_UNKNOWN")? += 1;
|
||||
outgoing
|
||||
.entry(from.node_id.as_str())
|
||||
.or_default()
|
||||
.push(to.node_id.as_str());
|
||||
}
|
||||
let mut queue = indegree
|
||||
.iter()
|
||||
.filter_map(|(id, degree)| (*degree == 0).then_some(*id))
|
||||
.collect::<VecDeque<_>>();
|
||||
let mut visited = 0usize;
|
||||
while let Some(node) = queue.pop_front() {
|
||||
visited += 1;
|
||||
for target in outgoing.get(node).into_iter().flatten() {
|
||||
let degree = indegree
|
||||
.get_mut(target)
|
||||
.ok_or("HOLOLAKE_COMPOSITION_EDGE_NODE_UNKNOWN")?;
|
||||
*degree -= 1;
|
||||
if *degree == 0 {
|
||||
queue.push_back(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
if visited != recipe.nodes.len() {
|
||||
return Err("HOLOLAKE_COMPOSITION_CYCLE_REJECTED".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view_module_id(view: ProjectionView) -> &'static str {
|
||||
match view {
|
||||
ProjectionView::Dashboard => "HLC-PROJECT-DASHBOARD",
|
||||
ProjectionView::Comparison => "HLC-PROJECT-COMPARISON",
|
||||
ProjectionView::VerticalBar => "HLC-PROJECT-VERTICAL-BAR",
|
||||
ProjectionView::Classification => "HLC-PROJECT-CLASSIFICATION",
|
||||
ProjectionView::Table => "HLC-PROJECT-TABLE",
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_value(value: (usize, u64, usize), measure: CompositionMeasure) -> u64 {
|
||||
match measure {
|
||||
CompositionMeasure::DocumentCount => value.0 as u64,
|
||||
CompositionMeasure::TotalBytes => value.1,
|
||||
CompositionMeasure::DuplicateCount => value.2 as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn top_level_folder(path: &str) -> String {
|
||||
let normalized = path.replace('\\', "/");
|
||||
let parts = normalized
|
||||
.split('/')
|
||||
.filter(|part| !part.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
// “导入/导入批次名”是转译外壳,不是人类真正要比较的知识分类。
|
||||
let candidate = if parts.first() == Some(&"导入") {
|
||||
parts.get(2).copied().unwrap_or("根页面")
|
||||
} else {
|
||||
parts.first().copied().unwrap_or("未分类")
|
||||
};
|
||||
if candidate.ends_with(".md") || candidate.ends_with(".markdown") || candidate.ends_with(".txt")
|
||||
{
|
||||
"根目录".into()
|
||||
} else {
|
||||
candidate.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn source_label(source: &str) -> String {
|
||||
match source {
|
||||
"native" => "光湖原生知识".into(),
|
||||
"legacy" => "待迁移知识".into(),
|
||||
other => other.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stable_key(value: &str) -> String {
|
||||
sha256(value.as_bytes())[..16].to_string()
|
||||
}
|
||||
fn sha256_json<T: Serialize>(value: &T) -> Result<String, String> {
|
||||
serde_json::to_vec(value)
|
||||
.map(|bytes| sha256(&bytes))
|
||||
.map_err(|error| format!("HOLOLAKE_COMPOSITION_DATA_SERIALIZE_FAILED: {error}"))
|
||||
}
|
||||
fn sha256(bytes: &[u8]) -> String {
|
||||
digest(&SHA256, bytes)
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|byte| format!("{byte:02x}"))
|
||||
.collect()
|
||||
}
|
||||
fn now_unix_ms() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|value| value.as_millis().min(u64::MAX as u128) as u64)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::knowledge_base::{KnowledgeDocumentSummary, KnowledgePageHeader, KnowledgeSnapshot};
|
||||
|
||||
fn page_header(number: &str, path: &str, hash: &str, version: u128) -> KnowledgePageHeader {
|
||||
KnowledgePageHeader {
|
||||
schema: "hololake.numbered-knowledge-page-header/v1",
|
||||
number: number.into(),
|
||||
numbering_system: "HLP-KB",
|
||||
parent_number: "HLP-KB-GRP-TEST".into(),
|
||||
path: format!("knowledge://native/{path}"),
|
||||
mapping_terms: Vec::new(),
|
||||
thought_summary: None,
|
||||
children: Vec::new(),
|
||||
source: "native".into(),
|
||||
version,
|
||||
content_sha256: hash.into(),
|
||||
state: "PENDING_THOUGHT_INDEX".into(),
|
||||
formal_routing: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot() -> KnowledgeSnapshot {
|
||||
KnowledgeSnapshot {
|
||||
schema: "hololake.native-knowledge-base/v1",
|
||||
state: "READY",
|
||||
native_root: "/private/account".into(),
|
||||
legacy_available: false,
|
||||
legacy_root: None,
|
||||
documents: vec![
|
||||
KnowledgeDocumentSummary {
|
||||
source: "native",
|
||||
path: "课程/第一课.md".into(),
|
||||
title: "第一课".into(),
|
||||
updated_at_unix_ms: 1,
|
||||
size_bytes: 100,
|
||||
content_sha256: "a".into(),
|
||||
duplicate_count: 1,
|
||||
page_header: page_header("HLP-KB-DOC-A", "课程/第一课.md", "a", 1),
|
||||
},
|
||||
KnowledgeDocumentSummary {
|
||||
source: "native",
|
||||
path: "课程/第二课.md".into(),
|
||||
title: "第二课".into(),
|
||||
updated_at_unix_ms: 2,
|
||||
size_bytes: 300,
|
||||
content_sha256: "b".into(),
|
||||
duplicate_count: 0,
|
||||
page_header: page_header("HLP-KB-DOC-B", "课程/第二课.md", "b", 2),
|
||||
},
|
||||
KnowledgeDocumentSummary {
|
||||
source: "native",
|
||||
path: "记录.md".into(),
|
||||
title: "记录".into(),
|
||||
updated_at_unix_ms: 3,
|
||||
size_bytes: 50,
|
||||
content_sha256: "c".into(),
|
||||
duplicate_count: 0,
|
||||
page_header: page_header("HLP-KB-DOC-C", "记录.md", "c", 3),
|
||||
},
|
||||
],
|
||||
raw_document_count: 4,
|
||||
unique_document_count: 3,
|
||||
duplicate_document_count: 1,
|
||||
truncated: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_native_rows_feed_all_projection_views_from_one_result() {
|
||||
let result = execute_snapshot(
|
||||
snapshot(),
|
||||
ExecuteCompositionInput {
|
||||
dimension: CompositionDimension::TopLevelFolder,
|
||||
measure: CompositionMeasure::TotalBytes,
|
||||
views: vec![
|
||||
ProjectionView::Dashboard,
|
||||
ProjectionView::VerticalBar,
|
||||
ProjectionView::Table,
|
||||
],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.state, "EXECUTED");
|
||||
assert_eq!(result.native_object.row_count, 3);
|
||||
assert_eq!(result.metrics.total_bytes, 450);
|
||||
assert_eq!(result.groups[0].label, "课程");
|
||||
assert_eq!(result.groups[0].measure_value, 400);
|
||||
assert!(result.source_is_real_account_data);
|
||||
assert!(result.read_only);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_or_empty_projection_requests_fail_closed() {
|
||||
assert!(validate_input(&ExecuteCompositionInput {
|
||||
dimension: CompositionDimension::Source,
|
||||
measure: CompositionMeasure::DocumentCount,
|
||||
views: vec![]
|
||||
})
|
||||
.is_err());
|
||||
assert!(validate_input(&ExecuteCompositionInput {
|
||||
dimension: CompositionDimension::Source,
|
||||
measure: CompositionMeasure::DocumentCount,
|
||||
views: vec![ProjectionView::Table, ProjectionView::Table]
|
||||
})
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_wrapper_folders_do_not_flatten_real_knowledge_categories() {
|
||||
assert_eq!(
|
||||
top_level_folder("导入/光湖语言世界/AI技能包/索引.md"),
|
||||
"AI技能包"
|
||||
);
|
||||
assert_eq!(top_level_folder("导入/光湖语言世界/首页.md"), "根目录");
|
||||
assert_eq!(top_level_folder("课程/第一课.md"), "课程");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_modules_type_mismatches_and_cycles_fail_closed() {
|
||||
let mut unknown = compile_recipe(&ExecuteCompositionInput {
|
||||
dimension: CompositionDimension::Source,
|
||||
measure: CompositionMeasure::DocumentCount,
|
||||
views: vec![ProjectionView::Table],
|
||||
});
|
||||
unknown.nodes[0].module_id = "UNKNOWN".into();
|
||||
assert_eq!(
|
||||
validate_recipe(&unknown, ®istry()).unwrap_err(),
|
||||
"HOLOLAKE_COMPOSITION_MODULE_NOT_REGISTERED"
|
||||
);
|
||||
|
||||
let mut mismatch = compile_recipe(&ExecuteCompositionInput {
|
||||
dimension: CompositionDimension::Source,
|
||||
measure: CompositionMeasure::DocumentCount,
|
||||
views: vec![ProjectionView::Table],
|
||||
});
|
||||
mismatch.edges[0] = CompositionEdge {
|
||||
from: "aggregate".into(),
|
||||
to: "classify".into(),
|
||||
};
|
||||
assert_eq!(
|
||||
validate_recipe(&mismatch, ®istry()).unwrap_err(),
|
||||
"HOLOLAKE_COMPOSITION_PORT_TYPE_MISMATCH"
|
||||
);
|
||||
|
||||
let cycle_registry = ModuleRegistry {
|
||||
schema: REGISTRY_SCHEMA,
|
||||
state: "TEST",
|
||||
modules: vec![
|
||||
descriptor("LOOP-A", "A", "TRANSFORM", Some("loop/v1"), "loop/v1"),
|
||||
descriptor("LOOP-B", "B", "TRANSFORM", Some("loop/v1"), "loop/v1"),
|
||||
],
|
||||
arbitrary_script_allowed: false,
|
||||
unregistered_module_allowed: false,
|
||||
direct_projection_write_allowed: false,
|
||||
};
|
||||
let cycle = CompositionRecipe {
|
||||
schema: RECIPE_SCHEMA,
|
||||
recipe_id: "TEST-CYCLE",
|
||||
title: "cycle",
|
||||
nodes: vec![
|
||||
CompositionNode {
|
||||
node_id: "a".into(),
|
||||
module_id: "LOOP-A".into(),
|
||||
},
|
||||
CompositionNode {
|
||||
node_id: "b".into(),
|
||||
module_id: "LOOP-B".into(),
|
||||
},
|
||||
],
|
||||
edges: vec![
|
||||
CompositionEdge {
|
||||
from: "a".into(),
|
||||
to: "b".into(),
|
||||
},
|
||||
CompositionEdge {
|
||||
from: "b".into(),
|
||||
to: "a".into(),
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
validate_recipe(&cycle, &cycle_registry).unwrap_err(),
|
||||
"HOLOLAKE_COMPOSITION_CYCLE_REJECTED"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue