From 6e89b82d8847313fb260f423dc045c7e1c838d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Fri, 21 Aug 2026 17:25:09 +0800 Subject: [PATCH] make TCS compiler GIR control validation and lowering --- bootstrap/tcs-stage0/tests/stage0.rs | 21 +++ build/self-host/compiler-A.gir.json | 46 +++++- build/self-host/compiler-B.gir.json | 46 +++++- language/compiler/TCS-COMPILER-STAGE1.tcs | 6 + native-runtime/tcs-gir-runtime/src/lib.rs | 169 ++++++++++++++++++++-- 5 files changed, 271 insertions(+), 17 deletions(-) diff --git a/bootstrap/tcs-stage0/tests/stage0.rs b/bootstrap/tcs-stage0/tests/stage0.rs index abadb3701..d185ae12e 100644 --- a/bootstrap/tcs-stage0/tests/stage0.rs +++ b/bootstrap/tcs-stage0/tests/stage0.rs @@ -142,3 +142,24 @@ fn compiler_gir_tampering_and_negative_program_fail_closed() { .expect_err("unregistered operation must still fail through compiler GIR"); assert_eq!(error.code, "TCS-E2101"); } + +#[test] +fn tcs_compiler_source_controls_operation_policy_and_gir_lowering() { + let compiler_source = compiler_source() + .replace( + "registered_operations = [\"CORE.ECHO\"]", + "registered_operations = [\"ABSORB\"]", + ) + .replace("to = \"exact_target\"", "to = \"policy_selected_target\""); + let compiler = tcs_stage0::bootstrap_compiler(&compiler_source).expect("policy compiler GIR"); + + let absorb_program = example().replace("CORE.ECHO", "ABSORB"); + let gir = tcs_stage0::compile_with_compiler_gir(&compiler, &absorb_program) + .expect("TCS source policy permits ABSORB at compile time"); + assert!(gir.get("policy_selected_target").is_some()); + assert!(gir.get("exact_target").is_none()); + + let error = tcs_stage0::compile_with_compiler_gir(&compiler, &example()) + .expect_err("TCS source policy rejects operation removed from its registry"); + assert_eq!(error.code, "TCS-E2101"); +} diff --git a/build/self-host/compiler-A.gir.json b/build/self-host/compiler-A.gir.json index 123c88ce5..aacee8933 100644 --- a/build/self-host/compiler-A.gir.json +++ b/build/self-host/compiler-A.gir.json @@ -2,7 +2,7 @@ "compiled_from": { "compiler_id": "TCS-STAGE0-IGNITION-0001", "compiler_state": "FOREIGN_HOST_BOOTSTRAP_SEED_NOT_SELF_HOSTED", - "source_sha256": "d375adb7a715c93320c14f3e38a3fa10bc764493d62cfa671e5dfd2d4d3b049f" + "source_sha256": "49259d15c57f3a725a7507ae746c372cebf9b3fc58dba580f7c41456d2344f73" }, "compiler_definition": { "canonicalization": { @@ -116,6 +116,26 @@ "from": "all", "to": "unresolved_natural_language", "transform": "MUST_BE_FALSE" + }, + "G013": { + "from": "inputs", + "to": "inputs", + "transform": "COPY_RESOLVED_VALUES" + }, + "G014": { + "from": "outputs", + "to": "outputs", + "transform": "COPY_RESOLVED_VALUES" + }, + "G015": { + "from": "conditions", + "to": "conditions", + "transform": "COPY_RESOLVED_CONDITIONS" + }, + "G016": { + "from": "failure", + "to": "failure_plan", + "transform": "COPY_EXPLICIT_FAILURE_SEMANTICS" } }, "output_contract": { @@ -237,7 +257,27 @@ "V010": { "error": "TCS-E4001", "rule": "RELATION_AND_EMOTION_DO_NOT_GRANT_AUTHORITY" - } + }, + "registered_operations": [ + "CORE.ECHO" + ], + "required_sections": [ + "header", + "source", + "subject", + "target", + "inputs", + "outputs", + "conditions", + "actions", + "authority", + "resources", + "failure", + "stop", + "cleanup", + "rollback", + "receipt" + ] }, "self_host": { "compiler_A_compiles_stage1_to": "compiler-B.gir", @@ -329,7 +369,7 @@ "execution_abi": "TCS-COMPILER-GIR-EXEC/1", "identity": { "compiler_id": "TCS-COMPILER-STAGE1-0001", - "definition_sha256": "c2bf0e4e93f9952daf661628aa7b17891718c53b9fbb5e77636f5f40812ff9f6", + "definition_sha256": "3e27046ef79f33c1f60705d6774773265df30e2299c390673ae9b8029c8235b2", "language_version": "0.1" }, "native_self_hosted": false, diff --git a/build/self-host/compiler-B.gir.json b/build/self-host/compiler-B.gir.json index 348a6b630..3ad79e5d0 100644 --- a/build/self-host/compiler-B.gir.json +++ b/build/self-host/compiler-B.gir.json @@ -2,7 +2,7 @@ "compiled_from": { "compiler_id": "TCS-COMPILER-STAGE1-0001", "compiler_state": "TCS_COMPILER_GIR_EXECUTED", - "source_sha256": "d375adb7a715c93320c14f3e38a3fa10bc764493d62cfa671e5dfd2d4d3b049f" + "source_sha256": "49259d15c57f3a725a7507ae746c372cebf9b3fc58dba580f7c41456d2344f73" }, "compiler_definition": { "canonicalization": { @@ -116,6 +116,26 @@ "from": "all", "to": "unresolved_natural_language", "transform": "MUST_BE_FALSE" + }, + "G013": { + "from": "inputs", + "to": "inputs", + "transform": "COPY_RESOLVED_VALUES" + }, + "G014": { + "from": "outputs", + "to": "outputs", + "transform": "COPY_RESOLVED_VALUES" + }, + "G015": { + "from": "conditions", + "to": "conditions", + "transform": "COPY_RESOLVED_CONDITIONS" + }, + "G016": { + "from": "failure", + "to": "failure_plan", + "transform": "COPY_EXPLICIT_FAILURE_SEMANTICS" } }, "output_contract": { @@ -237,7 +257,27 @@ "V010": { "error": "TCS-E4001", "rule": "RELATION_AND_EMOTION_DO_NOT_GRANT_AUTHORITY" - } + }, + "registered_operations": [ + "CORE.ECHO" + ], + "required_sections": [ + "header", + "source", + "subject", + "target", + "inputs", + "outputs", + "conditions", + "actions", + "authority", + "resources", + "failure", + "stop", + "cleanup", + "rollback", + "receipt" + ] }, "self_host": { "compiler_A_compiles_stage1_to": "compiler-B.gir", @@ -329,7 +369,7 @@ "execution_abi": "TCS-COMPILER-GIR-EXEC/1", "identity": { "compiler_id": "TCS-COMPILER-STAGE1-0001", - "definition_sha256": "c2bf0e4e93f9952daf661628aa7b17891718c53b9fbb5e77636f5f40812ff9f6", + "definition_sha256": "3e27046ef79f33c1f60705d6774773265df30e2299c390673ae9b8029c8235b2", "language_version": "0.1" }, "native_self_hosted": true, diff --git a/language/compiler/TCS-COMPILER-STAGE1.tcs b/language/compiler/TCS-COMPILER-STAGE1.tcs index ad7c5f554..b2c8c35a6 100644 --- a/language/compiler/TCS-COMPILER-STAGE1.tcs +++ b/language/compiler/TCS-COMPILER-STAGE1.tcs @@ -45,6 +45,8 @@ COMPILER TCS-COMPILER-STAGE1-0001 { floating_point = "FORBIDDEN_V0_1"; } program_validation { + required_sections = ["header", "source", "subject", "target", "inputs", "outputs", "conditions", "actions", "authority", "resources", "failure", "stop", "cleanup", "rollback", "receipt"]; + registered_operations = ["CORE.ECHO"]; V001 { rule = "EXACT_REQUIRED_SECTIONS"; standard = "TCS-FIELD-STANDARD-0001"; error_missing = "TCS-E1004"; error_unknown = "TCS-E1003"; } V002 { rule = "CLOSED_FIELDS_PER_SECTION"; error = "TCS-E1003"; } V003 { rule = "ALL_INPUT_OUTPUT_AND_ACTION_REFS_RESOLVE"; error = "TCS-E2002"; } @@ -69,6 +71,10 @@ COMPILER TCS-COMPILER-STAGE1-0001 { G010 { from = "rollback"; to = "rollback_plan"; transform = "COPY_ACTIONS_AND_VERIFICATION"; } G011 { from = "receipt"; to = "receipt_plan"; transform = "BIND_MACHINE_AND_HUMAN_OUTPUTS"; } G012 { from = "all"; to = "unresolved_natural_language"; transform = "MUST_BE_FALSE"; } + G013 { from = "inputs"; to = "inputs"; transform = "COPY_RESOLVED_VALUES"; } + G014 { from = "outputs"; to = "outputs"; transform = "COPY_RESOLVED_VALUES"; } + G015 { from = "conditions"; to = "conditions"; transform = "COPY_RESOLVED_CONDITIONS"; } + G016 { from = "failure"; to = "failure_plan"; transform = "COPY_EXPLICIT_FAILURE_SEMANTICS"; } } canonicalization { object_keys = "UTF8_BYTE_ORDER"; diff --git a/native-runtime/tcs-gir-runtime/src/lib.rs b/native-runtime/tcs-gir-runtime/src/lib.rs index d8da2f1c0..49105e46e 100644 --- a/native-runtime/tcs-gir-runtime/src/lib.rs +++ b/native-runtime/tcs-gir-runtime/src/lib.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value as JsonValue}; use sha2::{Digest, Sha256}; use std::{ - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fs, path::{Component, Path, PathBuf}, }; @@ -608,13 +608,26 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> { "Stage-0 executable subset accepts PROGRAM only", )); } - for section in PROGRAM_SECTIONS { + let sections = PROGRAM_SECTIONS + .iter() + .map(|value| (*value).to_owned()) + .collect::>(); + let operations = BTreeSet::from(["CORE.ECHO".to_owned()]); + validate_program_contract(document, §ions, &operations) +} + +fn validate_program_contract( + document: &TcsDocument, + sections: &[String], + registered_operations: &BTreeSet, +) -> Result<(), TcsError> { + for section in sections { document.body.get(section).ok_or_else(|| { TcsError::new("TCS-E1004", format!("required section {section} missing")) })?; } for section in document.body.keys() { - if !PROGRAM_SECTIONS.contains(§ion.as_str()) { + if !sections.contains(section) { return Err(TcsError::new( "TCS-E1003", format!("unknown PROGRAM section {section}"), @@ -647,7 +660,7 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> { for (action_id, action) in actions { let action = action.object(action_id)?; let operation = required_text(action, "operation")?; - if operation != "CORE.ECHO" { + if !registered_operations.contains(operation) { return Err(TcsError::new( "TCS-E2101", format!("unregistered TCS operation {operation}"), @@ -665,6 +678,99 @@ pub fn validate_program(document: &TcsDocument) -> Result<(), TcsError> { Ok(()) } +#[derive(Debug)] +struct CompilerPolicy { + required_sections: Vec, + registered_operations: BTreeSet, + lowerings: BTreeMap, + output_schema: String, +} + +fn compiler_policy(compiler: &JsonValue) -> Result { + fn text_array(value: &JsonValue, pointer: &str) -> Result, TcsError> { + value + .pointer(pointer) + .and_then(JsonValue::as_array) + .ok_or_else(|| TcsError::new("TCS-E3002", format!("missing array {pointer}")))? + .iter() + .map(|item| { + item.as_str().map(str::to_owned).ok_or_else(|| { + TcsError::new("TCS-E3002", format!("non-text item in {pointer}")) + }) + }) + .collect() + } + + let required_sections = text_array( + compiler, + "/compiler_definition/program_validation/required_sections", + )?; + let registered_operations = text_array( + compiler, + "/compiler_definition/program_validation/registered_operations", + )? + .into_iter() + .collect::>(); + if required_sections.is_empty() || registered_operations.is_empty() { + return Err(TcsError::new( + "TCS-E3002", + "compiler policy must not be empty", + )); + } + let lowering_rules = compiler + .pointer("/compiler_definition/lowering_to_gir") + .and_then(JsonValue::as_object) + .ok_or_else(|| TcsError::new("TCS-E3002", "lowering rules missing"))?; + let mut lowerings = BTreeMap::new(); + for rule in lowering_rules.values() { + let Some(from) = rule.get("from").and_then(JsonValue::as_str) else { + continue; + }; + let to = rule + .get("to") + .and_then(JsonValue::as_str) + .ok_or_else(|| TcsError::new("TCS-E3002", "lowering target missing"))?; + if document_section(from) && lowerings.insert(from.to_owned(), to.to_owned()).is_some() { + return Err(TcsError::new("TCS-E3002", "duplicate lowering source")); + } + } + let required_lowerings = [ + "subject", + "target", + "inputs", + "outputs", + "conditions", + "actions", + "authority", + "resources", + "failure", + "stop", + "cleanup", + "rollback", + "receipt", + ]; + if required_lowerings + .iter() + .any(|field| !lowerings.contains_key(*field)) + { + return Err(TcsError::new( + "TCS-E3002", + "compiler lowering map is incomplete", + )); + } + Ok(CompilerPolicy { + required_sections, + registered_operations, + lowerings, + output_schema: json_text(compiler, "/compiler_definition/output_contract/schema")? + .to_owned(), + }) +} + +fn document_section(value: &str) -> bool { + !matches!(value, "header" | "source" | "all") +} + fn validate_exact_sections(document: &TcsDocument, expected: &[&str]) -> Result<(), TcsError> { for section in expected { document.body.get(*section).ok_or_else(|| { @@ -759,6 +865,7 @@ pub fn sha256_hex(bytes: impl AsRef<[u8]>) -> String { format!("{:x}", Sha256::digest(bytes.as_ref())) } +#[cfg(feature = "bootstrap")] fn lower_program( document: &TcsDocument, source_sha256: &str, @@ -798,6 +905,44 @@ fn lower_program( })) } +fn lower_program_with_policy( + document: &TcsDocument, + source_sha256: &str, + compiler_id: &str, + policy: &CompilerPolicy, +) -> Result { + let mut gir = json!({ + "schema": policy.output_schema, + "identity": { + "gir_id": format!("GIR-{}", document.declaration_id), + "program_id": document.declaration_id, + "language_version": document.language_version, + }, + "compiled_from": { + "source_sha256": source_sha256, + "compiler_id": compiler_id, + "compiler_state": "TCS_COMPILER_GIR_EXECUTED", + }, + "unresolved_natural_language": false, + "native_self_hosted": true, + }); + let object = gir + .as_object_mut() + .ok_or_else(|| TcsError::new("TCS-E8001", "GIR root must be object"))?; + for (from, to) in &policy.lowerings { + let value = document + .body + .get(from) + .ok_or_else(|| TcsError::new("TCS-E3002", format!("lowering source {from} missing")))?; + object.insert( + to.clone(), + serde_json::to_value(value) + .map_err(|error| TcsError::new("TCS-E8001", error.to_string()))?, + ); + } + Ok(gir) +} + fn compiler_definition_sha256(document: &TcsDocument) -> Result { let definition = serde_json::to_vec(&document.body) .map_err(|error| TcsError::new("TCS-E8001", error.to_string()))?; @@ -922,18 +1067,20 @@ pub fn compile_with_compiler_gir( source: &str, ) -> Result { let (compiler_id, _) = verify_compiler_gir(compiler)?; + let policy = compiler_policy(compiler)?; let document = parse(source)?; let source_sha256 = sha256_hex(source.as_bytes()); match document.declaration_kind.as_str() { "PROGRAM" => { - validate_program(&document)?; - lower_program( + if document.language_version != "0.1" { + return Err(TcsError::new("TCS-E2102", "unsupported TCS version")); + } + validate_program_contract( &document, - &source_sha256, - compiler_id, - "TCS_COMPILER_GIR_EXECUTED", - true, - ) + &policy.required_sections, + &policy.registered_operations, + )?; + lower_program_with_policy(&document, &source_sha256, compiler_id, &policy) } "COMPILER" => { validate_compiler(&document)?;