feat: admit signed native composition module

This commit is contained in:
冰朔 2026-08-19 01:52:47 +08:00
commit 593d5e5884
22 changed files with 1735 additions and 28 deletions

View file

@ -15,6 +15,7 @@ mod knowledge_base;
mod local_development_bridge;
mod metacognitive_zero_layer;
mod module_package_runtime;
mod native_composition;
mod number_coordinate_tree;
mod numbered_ipc;
mod numbered_ipc_dispatch;

View file

@ -24,6 +24,13 @@ const CONTRACT_SCHEMA: &str = "hololake.module-package-runtime/v1";
const PACKAGE_SCHEMA: &str = "hololake.module-package/v1";
const HOST_VERSION: &str = "0.5.0";
const MAX_PACKAGE_BYTES: u64 = 16 * 1024 * 1024;
const NATIVE_COMPOSITION_NUMBER: &str = "HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001";
const NATIVE_COMPOSITION_PACKAGE: &[u8] = include_bytes!(
"../../fixtures/module-packages/HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001-0.1.0.ghmod"
);
const NATIVE_COMPOSITION_SIGNATURE: &str = include_str!(
"../../fixtures/module-packages/HLP-MOD-LOCAL-NATIVE-COMPOSITION-0001-0.1.0.ghmod.sig"
);
#[derive(Debug, Deserialize)]
struct RuntimeContract {
@ -151,6 +158,36 @@ pub struct ModuleMutationReceipt {
user_data_preserved: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ActivateBundledModuleInput {
module_number: String,
human_confirmed_permission_expansion: bool,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundledModuleDescriptor {
module_number: String,
display_name: String,
version: String,
adapter: String,
permissions: Vec<String>,
package_sha256: String,
installed_state: String,
signature_verified: bool,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundledModuleActivation {
schema: &'static str,
state: String,
module_number: String,
receipts: Vec<ModuleMutationReceipt>,
snapshot: ModuleRuntimeSnapshot,
}
fn now_unix_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -218,7 +255,11 @@ fn decode_outer_base64(value: &str, label: &str) -> Result<String, String> {
String::from_utf8(bytes).map_err(|error| format!("{label}_UTF8_INVALID: {error}"))
}
fn verify_signature(package: &[u8], signature_path: &Path, trust_raw: &str) -> Result<(), String> {
fn verify_signature_raw(
package: &[u8],
signature_raw: &str,
trust_raw: &str,
) -> Result<(), String> {
let trust: Value = serde_json::from_str(trust_raw)
.map_err(|error| format!("HOLOLAKE_MODULE_TRUST_INVALID: {error}"))?;
if trust.get("state").and_then(Value::as_str) != Some("PROVISIONED") {
@ -230,10 +271,8 @@ fn verify_signature(package: &[u8], signature_path: &Path, trust_raw: &str) -> R
.ok_or("HOLOLAKE_MODULE_TRUST_KEY_MISSING")?;
let public_key = PublicKey::decode(&decode_outer_base64(encoded_key, "HOLOLAKE_MODULE_KEY")?)
.map_err(|error| format!("HOLOLAKE_MODULE_KEY_INVALID: {error}"))?;
let signature_raw = fs::read_to_string(signature_path)
.map_err(|error| format!("HOLOLAKE_MODULE_SIGNATURE_READ_FAILED: {error}"))?;
let signature = Signature::decode(&decode_outer_base64(
&signature_raw,
signature_raw,
"HOLOLAKE_MODULE_SIGNATURE",
)?)
.map_err(|error| format!("HOLOLAKE_MODULE_SIGNATURE_INVALID: {error}"))?;
@ -321,12 +360,29 @@ fn read_verified_package(
}
let bytes = fs::read(package_path)
.map_err(|error| format!("HOLOLAKE_MODULE_PACKAGE_READ_FAILED: {error}"))?;
verify_signature(&bytes, signature_path, trust_raw)?;
let package: ModulePackage = serde_json::from_slice(&bytes)
let signature_raw = fs::read_to_string(signature_path)
.map_err(|error| format!("HOLOLAKE_MODULE_SIGNATURE_READ_FAILED: {error}"))?;
read_verified_package_bytes(&bytes, &signature_raw, trust_raw)
}
fn read_verified_package_bytes(
bytes: &[u8],
signature_raw: &str,
trust_raw: &str,
) -> Result<(Vec<u8>, ModulePackage, String), String> {
verify_signature_raw(bytes, signature_raw, trust_raw)?;
let package: ModulePackage = serde_json::from_slice(bytes)
.map_err(|error| format!("HOLOLAKE_MODULE_PACKAGE_JSON_INVALID: {error}"))?;
validate_package(&package, &contract()?)?;
let package_sha256 = sha256_hex(&bytes);
Ok((bytes, package, package_sha256))
let package_sha256 = sha256_hex(bytes);
Ok((bytes.to_vec(), package, package_sha256))
}
fn bundled_package(module_number: &str) -> Result<(&'static [u8], &'static str), String> {
match module_number {
NATIVE_COMPOSITION_NUMBER => Ok((NATIVE_COMPOSITION_PACKAGE, NATIVE_COMPOSITION_SIGNATURE)),
_ => Err("HOLOLAKE_BUNDLED_MODULE_UNKNOWN".into()),
}
}
fn runtime_root(app: &AppHandle) -> Result<PathBuf, String> {
@ -579,6 +635,175 @@ fn permissions_from_json(raw: &str) -> BTreeSet<String> {
.collect()
}
fn materialize_bundled_source(
root: &Path,
module_number: &str,
package: &[u8],
signature: &str,
) -> Result<(PathBuf, PathBuf), String> {
let source_root = root.join("bundled-sources");
fs::create_dir_all(&source_root)
.map_err(|error| format!("HOLOLAKE_BUNDLED_MODULE_SOURCE_ROOT_FAILED: {error}"))?;
let digest = sha256_hex(package);
let package_path = source_root.join(format!("{module_number}-{digest}.ghmod"));
let signature_path = source_root.join(format!("{module_number}-{digest}.ghmod.sig"));
for (path, bytes) in [
(&package_path, package),
(&signature_path, signature.as_bytes()),
] {
if path.exists() {
let current = fs::read(path)
.map_err(|error| format!("HOLOLAKE_BUNDLED_MODULE_SOURCE_READ_FAILED: {error}"))?;
if current != bytes {
return Err("HOLOLAKE_BUNDLED_MODULE_SOURCE_TAMPERED".into());
}
continue;
}
let temporary = source_root.join(format!(".bundled-{}", Uuid::new_v4()));
fs::write(&temporary, bytes)
.map_err(|error| format!("HOLOLAKE_BUNDLED_MODULE_SOURCE_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, path)
.map_err(|error| format!("HOLOLAKE_BUNDLED_MODULE_SOURCE_COMMIT_FAILED: {error}"))?;
}
Ok((package_path, signature_path))
}
pub async fn get_bundled_module_catalog(
app: AppHandle,
) -> Result<Vec<BundledModuleDescriptor>, String> {
let root = runtime_root(&app)?;
let snapshot = snapshot_at(&root)?;
let (bytes, signature) = bundled_package(NATIVE_COMPOSITION_NUMBER)?;
let (_, package, package_sha256) =
read_verified_package_bytes(bytes, signature, RELEASE_TRUST_RAW)?;
let installed_state = snapshot
.modules
.iter()
.find(|module| module.module_number == NATIVE_COMPOSITION_NUMBER)
.map(|module| module.state.clone())
.unwrap_or_else(|| "NOT_INSTALLED".into());
Ok(vec![BundledModuleDescriptor {
module_number: package.manifest.module_number,
display_name: package.manifest.display_name,
version: package.manifest.version,
adapter: package.manifest.adapter,
permissions: package.manifest.permissions,
package_sha256,
installed_state,
signature_verified: true,
}])
}
pub async fn activate_bundled_module(
app: AppHandle,
input: ActivateBundledModuleInput,
) -> Result<BundledModuleActivation, String> {
let root = runtime_root(&app)?;
let (bytes, signature) = bundled_package(&input.module_number)?;
let (_, package, package_sha256) =
read_verified_package_bytes(bytes, signature, RELEASE_TRUST_RAW)?;
if package.manifest.module_number != input.module_number {
return Err("HOLOLAKE_BUNDLED_MODULE_IDENTITY_INVALID".into());
}
let mut receipts = Vec::new();
let before = snapshot_at(&root)?;
let installed = before
.modules
.iter()
.find(|module| module.module_number == input.module_number);
if installed
.is_some_and(|module| module.state == "ACTIVE" && module.package_sha256 == package_sha256)
{
return Ok(BundledModuleActivation {
schema: "hololake.bundled-module-activation/v1",
state: "ALREADY_ACTIVE".into(),
module_number: input.module_number,
receipts,
snapshot: before,
});
}
let needs_install = installed
.map(|module| module.package_sha256 != package_sha256 || module.state == "FAILED_CLOSED")
.unwrap_or(true);
if needs_install {
let (package_path, signature_path) =
materialize_bundled_source(&root, &input.module_number, bytes, signature)?;
receipts.push(install_module_package_at(
&root,
&InstallModulePackageInput {
package_path: package_path.to_string_lossy().into_owned(),
signature_path: signature_path.to_string_lossy().into_owned(),
expected_package_sha256: package_sha256,
human_confirmed_permission_expansion: input.human_confirmed_permission_expansion,
},
RELEASE_TRUST_RAW,
now_unix_ms(),
)?);
}
let current = snapshot_at(&root)?
.modules
.into_iter()
.find(|module| module.module_number == input.module_number)
.ok_or("HOLOLAKE_BUNDLED_MODULE_ACTIVATION_STATE_MISSING")?;
match current.state.as_str() {
"INSTALLED_DORMANT" | "DORMANT" => {
receipts.push(transition(
&root,
&input.module_number,
&["INSTALLED_DORMANT", "DORMANT"],
"MOUNTED_PENDING_SELF_TEST",
"MOUNT",
)?);
receipts.push(self_test_module_at(
&root,
&input.module_number,
now_unix_ms(),
)?);
}
"MOUNTED_PENDING_SELF_TEST" | "ROLLBACK_PENDING_SELF_TEST" => {
receipts.push(self_test_module_at(
&root,
&input.module_number,
now_unix_ms(),
)?);
}
"ACTIVE" => {}
_ => return Err("HOLOLAKE_BUNDLED_MODULE_ACTIVATION_STATE_INVALID".into()),
}
let snapshot = snapshot_at(&root)?;
if !snapshot.modules.iter().any(|module| {
module.module_number == input.module_number
&& module.adapter == package.manifest.adapter
&& module.state == "ACTIVE"
}) {
return Err("HOLOLAKE_BUNDLED_MODULE_ACTIVATION_NOT_ACTIVE".into());
}
Ok(BundledModuleActivation {
schema: "hololake.bundled-module-activation/v1",
state: "ACTIVE".into(),
module_number: input.module_number,
receipts,
snapshot,
})
}
pub(crate) fn require_active_module_adapter(
app: &AppHandle,
module_number: &str,
adapter: &str,
) -> Result<(), String> {
let snapshot = snapshot_at(&runtime_root(app)?)?;
if snapshot.modules.iter().any(|module| {
module.module_number == module_number
&& module.adapter == adapter
&& module.state == "ACTIVE"
}) {
Ok(())
} else {
Err("HOLOLAKE_MODULE_NOT_ACTIVE".into())
}
}
pub async fn verify_module_package(
app: AppHandle,
input: VerifyModulePackageInput,
@ -1182,6 +1407,54 @@ mod tests {
assert_eq!(fs::read(user_data).unwrap(), b"preserve-after-unmount");
}
#[test]
fn bundled_native_composition_package_completes_installed_lifecycle() {
let temporary = tempfile::tempdir().unwrap();
let (_, package, digest) = read_verified_package_bytes(
NATIVE_COMPOSITION_PACKAGE,
NATIVE_COMPOSITION_SIGNATURE,
RELEASE_TRUST_RAW,
)
.unwrap();
assert_eq!(package.manifest.module_number, NATIVE_COMPOSITION_NUMBER);
assert_eq!(package.manifest.adapter, "native-composition-v1");
let (package_path, signature_path) = materialize_bundled_source(
temporary.path(),
NATIVE_COMPOSITION_NUMBER,
NATIVE_COMPOSITION_PACKAGE,
NATIVE_COMPOSITION_SIGNATURE,
)
.unwrap();
install_module_package_at(
temporary.path(),
&InstallModulePackageInput {
package_path: package_path.to_string_lossy().into_owned(),
signature_path: signature_path.to_string_lossy().into_owned(),
expected_package_sha256: digest,
human_confirmed_permission_expansion: true,
},
RELEASE_TRUST_RAW,
200,
)
.unwrap();
transition(
temporary.path(),
NATIVE_COMPOSITION_NUMBER,
&["INSTALLED_DORMANT"],
"MOUNTED_PENDING_SELF_TEST",
"MOUNT",
)
.unwrap();
self_test_module_at(temporary.path(), NATIVE_COMPOSITION_NUMBER, 202).unwrap();
let snapshot = snapshot_at(temporary.path()).unwrap();
assert!(snapshot.modules.iter().any(|module| {
module.module_number == NATIVE_COMPOSITION_NUMBER
&& module.adapter == "native-composition-v1"
&& module.state == "ACTIVE"
}));
assert_eq!(snapshot.receipt_count, 3);
}
#[cfg(unix)]
#[test]
fn symlinked_package_input_is_rejected_before_signature_processing() {

View file

@ -0,0 +1,818 @@
//! 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, &registry())?;
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, KnowledgeSnapshot};
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,
},
KnowledgeDocumentSummary {
source: "native",
path: "课程/第二课.md".into(),
title: "第二课".into(),
updated_at_unix_ms: 2,
size_bytes: 300,
content_sha256: "b".into(),
duplicate_count: 0,
},
KnowledgeDocumentSummary {
source: "native",
path: "记录.md".into(),
title: "记录".into(),
updated_at_unix_ms: 3,
size_bytes: 50,
content_sha256: "c".into(),
duplicate_count: 0,
},
],
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, &registry()).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, &registry()).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"
);
}
}

View file

@ -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 != 91
|| tree.route_count != 95
|| tree.routes.len() != tree.route_count
|| !tree.invariants.number_is_stable_coordinate_not_authority
|| !tree.invariants.path_is_unique_navigation

View file

@ -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(), 69);
assert_eq!(registry.operations.len(), 73);
assert!(!registry.runtime.legacy_direct_commands_allowed);
}

View file

@ -120,6 +120,19 @@ pub(crate) async fn dispatch(
"module_package_runtime::rollback_module" => {
json(crate::module_package_runtime::rollback_module(app, input(&payload)?).await?)
}
"module_package_runtime::get_bundled_module_catalog" => {
json(crate::module_package_runtime::get_bundled_module_catalog(app).await?)
}
"module_package_runtime::activate_bundled_module" => json(
crate::module_package_runtime::activate_bundled_module(app, input(&payload)?).await?,
),
"native_composition::get_native_composition_module_registry" => {
json(crate::native_composition::get_native_composition_module_registry())
}
"native_composition::execute_knowledge_native_composition" => json(
crate::native_composition::execute_knowledge_native_composition(app, input(&payload)?)
.await?,
),
"local_development_bridge::acquire_development_write_lane" => json(
crate::local_development_bridge::acquire_development_write_lane(app, input(&payload)?)
.await?,