make TCS compiler GIR control validation and lowering

This commit is contained in:
冰朔 2026-08-21 17:25:09 +08:00
commit 6e89b82d88
5 changed files with 271 additions and 17 deletions

View file

@ -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::<Vec<_>>();
let operations = BTreeSet::from(["CORE.ECHO".to_owned()]);
validate_program_contract(document, &sections, &operations)
}
fn validate_program_contract(
document: &TcsDocument,
sections: &[String],
registered_operations: &BTreeSet<String>,
) -> 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(&section.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<String>,
registered_operations: BTreeSet<String>,
lowerings: BTreeMap<String, String>,
output_schema: String,
}
fn compiler_policy(compiler: &JsonValue) -> Result<CompilerPolicy, TcsError> {
fn text_array(value: &JsonValue, pointer: &str) -> Result<Vec<String>, 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::<BTreeSet<_>>();
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<JsonValue, TcsError> {
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<String, TcsError> {
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<JsonValue, TcsError> {
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)?;