make TCS compiler GIR control validation and lowering
This commit is contained in:
parent
14ffe45caa
commit
6e89b82d88
5 changed files with 271 additions and 17 deletions
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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, §ions, &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(§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<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)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue