S03-H3 CLOSED baseline

This commit is contained in:
2026-08-27 11:12:22 +02:00
commit 4b5fbb6c23
30 changed files with 10130 additions and 0 deletions
+508
View File
@@ -0,0 +1,508 @@
#!/usr/bin/env bash
set -u
set -o pipefail
readonly MINIMUM_SAMPLES="50"
readonly EXPECTED_PARSER_SHA256="91c4d66770197e1986ed91eb6a4870e7da7df5a522ffdf4329b6d9250f238307"
readonly EXPECTED_H1_VERIFIER_SHA256="2f1c41b186bd2e79a9fb51fab0461a7a0dce104bf0852c0ec1ade2c55a017443"
readonly EXPECTED_TEST_CONFIG_SHA256="d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e"
readonly EXPECTED_DIAGNOSTIC_CONFIG_SHA256="cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
SUITE="$(cd -- "$SCRIPT_DIR/../.." && pwd -P)"
PARSER="$SUITE/scenarios/S03_semantic_sentinels/parse_s03_binary.py"
H1_VERIFIER="$SUITE/scenarios/S03H1_execution_profiles/verify_s03h1_config.py"
MINIMAL_VERIFIER="$SCRIPT_DIR/verify_s03h3_minimal_structure.py"
TEST_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_test_from_s02.marte"
DIAGNOSTIC_CONFIG="$SUITE/configurations/generated/S03H1/from_patcher/smoke_s03h1_execution_profiles_diagnostic_from_s02.marte"
MINIMAL_CONFIG="$SUITE/configurations/generated/S03H3/from_patcher/smoke_s03h3_execution_profiles_minimal_from_s03h1_test.marte"
MODE="NOT_PROVIDED"
MODE_STATUS="NOT_DEMONSTRATED"
PROFILE_CONFIG_STATUS="NOT_DEMONSTRATED"
PROFILE_RUNTIME_STATUS="NOT_DEMONSTRATED"
OBSERVABILITY_STATUS="NOT_DEMONSTRATED"
OBSERVABILITY_REASON="NOT_EVALUATED"
FUNCTIONAL_STATUS="NOT_DEMONSTRATED"
SEMANTIC_STATUS="NOT_DEMONSTRATED"
TIMING_STATUS="NOT_EVALUATED"
MEASUREMENT_ELIGIBILITY="NO"
COMPLETE_SAMPLE_COUNT="NOT_DEMONSTRATED"
CONFIG_REPORT=""
RUNTIME_CONFIG_REPORT=""
BINARY_REPORT=""
HASH_REPORT=""
cleanup() {
rm -f -- \
"${CONFIG_REPORT:-}" \
"${RUNTIME_CONFIG_REPORT:-}" \
"${BINARY_REPORT:-}" \
"${HASH_REPORT:-}" 2>/dev/null || true
}
trap cleanup EXIT HUP INT TERM
emit_contract() {
printf 'MODE=%s\n' "$MODE"
printf 'MODE_STATUS=%s\n' "$MODE_STATUS"
printf 'PROFILE_CONFIG_STATUS=%s\n' "$PROFILE_CONFIG_STATUS"
printf 'PROFILE_RUNTIME_STATUS=%s\n' "$PROFILE_RUNTIME_STATUS"
printf 'OBSERVABILITY_STATUS=%s\n' "$OBSERVABILITY_STATUS"
printf 'OBSERVABILITY_REASON=%s\n' "$OBSERVABILITY_REASON"
printf 'FUNCTIONAL_STATUS=%s\n' "$FUNCTIONAL_STATUS"
printf 'SEMANTIC_STATUS=%s\n' "$SEMANTIC_STATUS"
printf 'TIMING_STATUS=%s\n' "$TIMING_STATUS"
printf 'MEASUREMENT_ELIGIBILITY=%s\n' "$MEASUREMENT_ELIGIBILITY"
printf 'COMPLETE_SAMPLE_COUNT=%s\n' "$COMPLETE_SAMPLE_COUNT"
}
fail_early() {
local code="$1"
local reason="$2"
printf '[NON DIMOSTRATO] %s\n' "$reason" >&2
OBSERVABILITY_REASON="$reason"
emit_contract
printf 'VALIDATION_RESULT=FAIL_REQUIRED_INPUT\n'
exit "$code"
}
count_lines() {
local pattern="$1"
local file="$2"
grep -Ec "$pattern" "$file" || true
}
report_value_exact() {
local key="$1"
local file="$2"
local count
count="$(grep -Ec "^${key}=" "$file" || true)"
if [ "$count" -ne 1 ]; then
return 1
fi
sed -n "s/^${key}=//p" "$file"
}
verify_identity() {
local file="$1"
local expected="$2"
[ -f "$file" ] || return 1
[ "$(sha256sum -- "$file" | awk '{print $1}')" = "$expected" ]
}
if [ "$#" -ne 2 ]; then
printf 'Uso: %s MINIMAL|TEST|DIAGNOSTIC <directory-run|run.log>\n' "$0" >&2
emit_contract
printf 'VALIDATION_RESULT=FAIL_INVALID_USAGE\n'
exit 2
fi
MODE="$1"
case "$MODE" in
MINIMAL)
SELECTED_CONFIG="$MINIMAL_CONFIG"
;;
TEST)
SELECTED_CONFIG="$TEST_CONFIG"
EXPECTED_SELECTED_CONFIG_SHA256="$EXPECTED_TEST_CONFIG_SHA256"
;;
DIAGNOSTIC)
SELECTED_CONFIG="$DIAGNOSTIC_CONFIG"
EXPECTED_SELECTED_CONFIG_SHA256="$EXPECTED_DIAGNOSTIC_CONFIG_SHA256"
;;
*)
MODE_STATUS="FAIL"
PROFILE_CONFIG_STATUS="FAIL"
PROFILE_RUNTIME_STATUS="FAIL"
OBSERVABILITY_STATUS="FAIL"
OBSERVABILITY_REASON="UNKNOWN_MODE"
emit_contract
printf 'VALIDATION_RESULT=FAIL_UNKNOWN_MODE\n'
exit 2
;;
esac
INPUT_PATH="$2"
if [ -d "$INPUT_PATH" ]; then
RUN_DIR="$(cd -- "$INPUT_PATH" && pwd -P)"
RUN_LOG="$RUN_DIR/run.log"
elif [ -f "$INPUT_PATH" ]; then
RUN_LOG="$(cd -- "$(dirname -- "$INPUT_PATH")" && pwd -P)/$(basename -- "$INPUT_PATH")"
RUN_DIR="$(dirname -- "$RUN_LOG")"
else
fail_early 21 "INPUT_PATH_NOT_FOUND"
fi
RUN_CONFIG="$RUN_DIR/configuration.marte"
OUTPUT_FILE="$RUN_DIR/s03_output.bin"
RUNNER_SUMMARY="$RUN_DIR/runner_summary.txt"
RUN_HASHES="$RUN_DIR/hashes.sha256"
MODE_FILE="$RUN_DIR/s03h3_mode.txt"
verify_identity "$PARSER" "$EXPECTED_PARSER_SHA256" || \
fail_early 21 "PARSER_IDENTITY_NOT_DEMONSTRATED"
verify_identity "$H1_VERIFIER" "$EXPECTED_H1_VERIFIER_SHA256" || \
fail_early 21 "H1_VERIFIER_IDENTITY_NOT_DEMONSTRATED"
[ -x "$MINIMAL_VERIFIER" ] || fail_early 21 "MINIMAL_VERIFIER_NOT_EXECUTABLE"
for required_file in \
"$RUN_LOG" \
"$RUN_CONFIG" \
"$OUTPUT_FILE" \
"$RUNNER_SUMMARY" \
"$RUN_HASHES" \
"$MODE_FILE" \
"$SELECTED_CONFIG"
do
[ -f "$required_file" ] || fail_early 21 "REQUIRED_FILE_MISSING"
done
[ -s "$RUN_LOG" ] || fail_early 21 "RUN_LOG_EMPTY"
[ -s "$RUN_HASHES" ] || fail_early 21 "RUN_HASH_MANIFEST_EMPTY"
SELECTED_CONFIG_SHA256="$(sha256sum -- "$SELECTED_CONFIG" | awk '{print $1}')"
if [ "$MODE" = "MINIMAL" ]; then
EXPECTED_SELECTED_CONFIG_SHA256="$SELECTED_CONFIG_SHA256"
fi
CANONICAL_CONFIG_IDENTITY_STATUS="PASS"
if [ "$SELECTED_CONFIG_SHA256" != "$EXPECTED_SELECTED_CONFIG_SHA256" ]; then
CANONICAL_CONFIG_IDENTITY_STATUS="FAIL"
fi
MODE_FILE_CONTENT_STATUS="PASS"
MODE_DECLARATION_COUNT=0
SCENARIO_DECLARATION_COUNT=0
CONFIG_HASH_DECLARATION_COUNT=0
while IFS= read -r metadata_line || [ -n "$metadata_line" ]; do
case "$metadata_line" in
'')
;;
SCENARIO=S03H3)
SCENARIO_DECLARATION_COUNT=$((SCENARIO_DECLARATION_COUNT + 1))
;;
MODE=MINIMAL|MODE=TEST|MODE=DIAGNOSTIC)
MODE_DECLARATION_COUNT=$((MODE_DECLARATION_COUNT + 1))
[ "${metadata_line#MODE=}" = "$MODE" ] || MODE_FILE_CONTENT_STATUS="FAIL"
;;
SELECTED_CONFIG_SHA256=*)
CONFIG_HASH_DECLARATION_COUNT=$((CONFIG_HASH_DECLARATION_COUNT + 1))
declared_config_sha256="${metadata_line#SELECTED_CONFIG_SHA256=}"
if [[ ! "$declared_config_sha256" =~ ^[0-9a-f]{64}$ ]] ||
[ "$declared_config_sha256" != "$EXPECTED_SELECTED_CONFIG_SHA256" ] ||
[ "$declared_config_sha256" != "$SELECTED_CONFIG_SHA256" ]; then
MODE_FILE_CONTENT_STATUS="FAIL"
fi
;;
*)
MODE_FILE_CONTENT_STATUS="FAIL"
;;
esac
done <"$MODE_FILE"
if [ "$MODE_FILE_CONTENT_STATUS" = "PASS" ] &&
[ "$CANONICAL_CONFIG_IDENTITY_STATUS" = "PASS" ] &&
[ "$MODE_DECLARATION_COUNT" -eq 1 ] &&
[ "$SCENARIO_DECLARATION_COUNT" -eq 1 ] &&
[ "$CONFIG_HASH_DECLARATION_COUNT" -eq 1 ]; then
MODE_STATUS="PASS"
else
MODE_STATUS="FAIL"
fi
CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_config.XXXXXX")" || \
fail_early 21 "CONFIG_REPORT_CREATION_FAILED"
RUNTIME_CONFIG_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_runtime.XXXXXX")" || \
fail_early 21 "RUNTIME_REPORT_CREATION_FAILED"
BINARY_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_binary.XXXXXX")" || \
fail_early 21 "BINARY_REPORT_CREATION_FAILED"
HASH_REPORT="$(mktemp "${TMPDIR:-/tmp}/s03h3_hashes.XXXXXX")" || \
fail_early 21 "HASH_REPORT_CREATION_FAILED"
EXPECTED_FILENAME="$RUN_DIR/s03_output.bin"
CONFIG_RC=1
RUNTIME_CONFIG_RC=1
CANONICAL_STRUCTURAL_COMPLETENESS_STATUS="NOT_APPLICABLE"
MINIMAL_RUNTIME_CONFIG_STATUS="NOT_APPLICABLE"
if [ "$MODE" = "MINIMAL" ]; then
PYTHONHASHSEED=0 python3 "$MINIMAL_VERIFIER" \
CANONICAL "$TEST_CONFIG" "$MINIMAL_CONFIG" \
>"$CONFIG_REPORT" 2>&1
CONFIG_RC=$?
PYTHONHASHSEED=0 python3 "$MINIMAL_VERIFIER" \
RUNTIME "$MINIMAL_CONFIG" "$RUN_CONFIG" "$RUN_DIR" \
>"$RUNTIME_CONFIG_REPORT" 2>&1
RUNTIME_CONFIG_RC=$?
CANONICAL_STRUCTURAL_COMPLETENESS_STATUS="$(
report_value_exact CANONICAL_STRUCTURAL_COMPLETENESS_STATUS "$CONFIG_REPORT" || printf FAIL
)"
MINIMAL_RUNTIME_CONFIG_STATUS="$(
report_value_exact MINIMAL_RUNTIME_CONFIG_STATUS "$RUNTIME_CONFIG_REPORT" || printf FAIL
)"
if [ "$CONFIG_RC" -eq 0 ] &&
[ "$RUNTIME_CONFIG_RC" -eq 0 ] &&
[ "$CANONICAL_STRUCTURAL_COMPLETENESS_STATUS" = "PASS" ] &&
[ "$MINIMAL_RUNTIME_CONFIG_STATUS" = "PASS" ]; then
PROFILE_CONFIG_STATUS="PASS"
else
PROFILE_CONFIG_STATUS="FAIL"
fi
else
PYTHONHASHSEED=0 python3 "$H1_VERIFIER" \
"$MODE" "$RUN_CONFIG" "$EXPECTED_FILENAME" \
>"$CONFIG_REPORT" 2>&1
CONFIG_RC=$?
RUNTIME_CONFIG_RC=0
PROFILE_CONFIG_STATUS="$(
report_value_exact CONFIG_PROFILE_VERIFICATION_STATUS "$CONFIG_REPORT" || printf FAIL
)"
fi
GAMTIMER_RUNTIME_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function GAMTimer \[idx: 0\][[:space:]]*$' "$RUN_LOG")"
SENTINEL_RUNTIME_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function SentinelProducer \[idx: 1\][[:space:]]*$' "$RUN_LOG")"
GAMDISPLAY_IDX2_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function GAMDisplay \[idx: 2\][[:space:]]*$' "$RUN_LOG")"
IOGAM_IDX2_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function IOGAM_Writer \[idx: 2\][[:space:]]*$' "$RUN_LOG")"
IOGAM_IDX3_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function IOGAM_Writer \[idx: 3\][[:space:]]*$' "$RUN_LOG")"
FUNCTION_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving for function .* \[idx: [0-9]+\][[:space:]]*$' "$RUN_LOG")"
GAMTIMER_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving GAMTimer[[:space:]]*$' "$RUN_LOG")"
SENTINEL_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving SentinelProducer[[:space:]]*$' "$RUN_LOG")"
GAMDISPLAY_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving GAMDisplay[[:space:]]*$' "$RUN_LOG")"
IOGAM_OBJECT_RESOLUTION_LINES="$(count_lines '^\[Information - RealTimeApplicationConfigurationBuilder\.cpp:[0-9]+\]: Resolving IOGAM_Writer[[:space:]]*$' "$RUN_LOG")"
case "$MODE" in
MINIMAL)
if [ "$GAMTIMER_RUNTIME_LINES" -eq 1 ] &&
[ "$SENTINEL_RUNTIME_LINES" -eq 1 ] &&
[ "$GAMDISPLAY_IDX2_LINES" -eq 0 ] &&
[ "$IOGAM_IDX2_LINES" -eq 1 ] &&
[ "$IOGAM_IDX3_LINES" -eq 0 ] &&
[ "$GAMTIMER_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$SENTINEL_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$GAMDISPLAY_OBJECT_RESOLUTION_LINES" -eq 0 ] &&
[ "$IOGAM_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$FUNCTION_RESOLUTION_LINES" -eq 3 ]; then
PROFILE_RUNTIME_STATUS="PASS"
else
PROFILE_RUNTIME_STATUS="FAIL"
fi
;;
TEST|DIAGNOSTIC)
if [ "$GAMTIMER_RUNTIME_LINES" -eq 1 ] &&
[ "$SENTINEL_RUNTIME_LINES" -eq 1 ] &&
[ "$GAMDISPLAY_IDX2_LINES" -eq 1 ] &&
[ "$IOGAM_IDX2_LINES" -eq 0 ] &&
[ "$IOGAM_IDX3_LINES" -eq 1 ] &&
[ "$GAMTIMER_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$SENTINEL_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$GAMDISPLAY_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$IOGAM_OBJECT_RESOLUTION_LINES" -eq 1 ] &&
[ "$FUNCTION_RESOLUTION_LINES" -eq 4 ]; then
PROFILE_RUNTIME_STATUS="PASS"
else
PROFILE_RUNTIME_STATUS="FAIL"
fi
;;
esac
PYTHONHASHSEED=0 python3 "$PARSER" "$OUTPUT_FILE" --min-samples "$MINIMUM_SAMPLES" \
>"$BINARY_REPORT" 2>&1
PARSER_RC=$?
sha256sum -c --strict "$RUN_HASHES" >"$HASH_REPORT" 2>&1
HASH_RC=$?
FUNCTIONAL_BINARY_STATUS="$(report_value_exact FUNCTIONAL_BINARY_STATUS "$BINARY_REPORT" || printf FAIL)"
SEMANTIC_BINARY_STATUS="$(report_value_exact SEMANTIC_BINARY_STATUS "$BINARY_REPORT" || printf FAIL)"
BINARY_STATUS="$(report_value_exact BINARY_STATUS "$BINARY_REPORT" || printf FAIL)"
PARSER_PAYLOAD_ALIGNMENT_STATUS="$(report_value_exact PAYLOAD_ALIGNMENT_STATUS "$BINARY_REPORT" || printf FAIL)"
COMPLETE_SAMPLE_COUNT="$(report_value_exact COMPLETE_SAMPLE_COUNT "$BINARY_REPORT" || printf NOT_DEMONSTRATED)"
COMPLETE_SAMPLE_COUNT_STATUS="FAIL"
case "$COMPLETE_SAMPLE_COUNT" in
''|*[!0-9]*) ;;
*)
if [ "$PARSER_RC" -eq 0 ] &&
[ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] &&
[ "$SEMANTIC_BINARY_STATUS" = "PASS" ] &&
[ "$BINARY_STATUS" = "PASS" ] &&
[ "$PARSER_PAYLOAD_ALIGNMENT_STATUS" = "PASS" ]; then
COMPLETE_SAMPLE_COUNT_STATUS="PASS"
fi
;;
esac
LOGGER_SIGNAL_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: (Counter|Time|State1_Thread1_CycleTime|SentinelScalarA|SentinelScalarB|SentinelVector) \[[0-9]+:[0-9]+\]:' "$RUN_LOG")"
LOGGER_COUNTER_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Counter \[0:0\]:' "$RUN_LOG")"
LOGGER_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: Time \[0:0\]:' "$RUN_LOG")"
LOGGER_CYCLE_TIME_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: State1_Thread1_CycleTime \[0:0\]:' "$RUN_LOG")"
LOGGER_SCALAR_A_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarA \[0:0\]:' "$RUN_LOG")"
LOGGER_SCALAR_B_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelScalarB \[0:0\]:' "$RUN_LOG")"
LOGGER_VECTOR_LINES="$(count_lines '^\[Information - LoggerBroker\.cpp:[0-9]+\]: SentinelVector \[0:2\]:' "$RUN_LOG")"
if [ "$COMPLETE_SAMPLE_COUNT_STATUS" != "PASS" ]; then
OBSERVABILITY_STATUS="NOT_DEMONSTRATED"
OBSERVABILITY_REASON="COMPLETE_SAMPLE_COUNT_NOT_AUTHORITATIVE"
elif [ "$MODE" = "MINIMAL" ] || [ "$MODE" = "TEST" ]; then
if [ "$LOGGER_SIGNAL_LINES" -eq 0 ] &&
[ "$LOGGER_COUNTER_LINES" -eq 0 ] &&
[ "$LOGGER_TIME_LINES" -eq 0 ] &&
[ "$LOGGER_CYCLE_TIME_LINES" -eq 0 ] &&
[ "$LOGGER_SCALAR_A_LINES" -eq 0 ] &&
[ "$LOGGER_SCALAR_B_LINES" -eq 0 ] &&
[ "$LOGGER_VECTOR_LINES" -eq 0 ]; then
OBSERVABILITY_STATUS="PASS"
OBSERVABILITY_REASON="NONE"
else
OBSERVABILITY_STATUS="FAIL"
OBSERVABILITY_REASON="UNEXPECTED_LOGGER_SIGNAL_LINES"
fi
else
if [ "$LOGGER_COUNTER_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_TIME_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_CYCLE_TIME_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_SCALAR_A_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_SCALAR_B_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_VECTOR_LINES" -eq "$COMPLETE_SAMPLE_COUNT" ] &&
[ "$LOGGER_SIGNAL_LINES" -eq $((6 * COMPLETE_SAMPLE_COUNT)) ]; then
OBSERVABILITY_STATUS="PASS"
OBSERVABILITY_REASON="NONE"
elif [ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_TIME_LINES" ] &&
[ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_CYCLE_TIME_LINES" ] &&
[ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_SCALAR_A_LINES" ] &&
[ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_SCALAR_B_LINES" ] &&
[ "$LOGGER_COUNTER_LINES" -eq "$LOGGER_VECTOR_LINES" ]; then
OBSERVABILITY_STATUS="NOT_DEMONSTRATED"
OBSERVABILITY_REASON="SHUTDOWN_SKEW"
else
OBSERVABILITY_STATUS="FAIL"
OBSERVABILITY_REASON="LOGGER_BINARY_COUNT_MISMATCH"
fi
fi
START_LINES="$(count_lines '^\[Information - RealTimeLoader\.cpp:[0-9]+\]: Started application in state State1[[:space:]]*$' "$RUN_LOG")"
APPLICATION_START_LINES="$(count_lines '^\[Information - MARTeApp\.cpp:[0-9]+\]: Application starting[[:space:]]*$' "$RUN_LOG")"
FILE_OPEN_LINES="$(count_lines '^\[Information - FileWriter\.cpp:[0-9]+\]: Going to open file with name ' "$RUN_LOG")"
SIGINT_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application recieved SIGINT\.[[:space:]]*$' "$RUN_LOG")"
STOP_OK_LINES="$(count_lines '^\[Information - Bootstrap\.cpp:[0-9]+\]: Application successfully stopped\.[[:space:]]*$' "$RUN_LOG")"
TERMINATED_LINES="$(count_lines '^\[NoError - MARTeApp\.cpp:[0-9]+\]: Application terminated[[:space:]]*$' "$RUN_LOG")"
STARTUP_ERROR_LINES="$(count_lines 'Failed dlopen|Failed CreateByName|Failed to Initialise object|Failed to initialise the ObjectRegistryDatabase|Could not Initialise the loader' "$RUN_LOG")"
FATAL_ERROR_LINES="$(count_lines '^\[FatalError ' "$RUN_LOG")"
OS_ERROR_LINES="$(count_lines '^\[OSError ' "$RUN_LOG")"
ERROR_LINES="$(count_lines '^\[Error ' "$RUN_LOG")"
INITIALISATION_ERROR_LINES="$(count_lines '^\[InitialisationEr ' "$RUN_LOG")"
PRIORITY_FAIL_LINES="$(count_lines '^\[Warning - Threads\.cpp:[0-9]+\]: Failed to change the thread priority' "$RUN_LOG")"
DOUBLE_STOP_LINES="$(count_lines '^\[FatalError - RealTimeApplication\.cpp:[0-9]+\]: Could not stop the RealTimeApplication\. Was it ever started\?[[:space:]]*$' "$RUN_LOG")"
ASYNC_CLOSE_LINES="$(count_lines '^\[FatalError - MemoryMapAsyncOutputBroker\.cpp:[0-9]+\]: Could not Close the EventSem\.[[:space:]]*$' "$RUN_LOG")"
MUTEX_DESTROY_LINES="$(count_lines '^\[OSError - EventSem\.cpp:[0-9]+\]: Error: pthread_mutex_destroy\(\)[[:space:]]*$' "$RUN_LOG")"
UNCLASSIFIED_FATAL_LINES=$((FATAL_ERROR_LINES - DOUBLE_STOP_LINES - ASYNC_CLOSE_LINES))
UNCLASSIFIED_OS_ERROR_LINES=$((OS_ERROR_LINES - MUTEX_DESTROY_LINES))
[ "$UNCLASSIFIED_FATAL_LINES" -ge 0 ] || UNCLASSIFIED_FATAL_LINES=0
[ "$UNCLASSIFIED_OS_ERROR_LINES" -ge 0 ] || UNCLASSIFIED_OS_ERROR_LINES=0
MARTE_EXIT_CODE="$(sed -n 's/^Exit code MARTe:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)"
TEE_EXIT_CODE="$(sed -n 's/^Exit code tee:[[:space:]]*//p' "$RUNNER_SUMMARY" | tail -n 1)"
[ -n "$MARTE_EXIT_CODE" ] || MARTE_EXIT_CODE="NOT_AVAILABLE"
[ -n "$TEE_EXIT_CODE" ] || TEE_EXIT_CODE="NOT_AVAILABLE"
case "$MARTE_EXIT_CODE" in
137) TIMING_STATUS="NOT_VALID_EXIT_137_SIGKILL" ;;
*)
if [ "$PRIORITY_FAIL_LINES" -ge 1 ]; then
TIMING_STATUS="NOT_VALID_PRIORITY_NOT_APPLIED"
else
TIMING_STATUS="UNASSESSED_DT_SEMANTICS"
fi
;;
esac
if [ "$MODE_STATUS" = "PASS" ] &&
[ "$PROFILE_CONFIG_STATUS" = "PASS" ] &&
[ "$PROFILE_RUNTIME_STATUS" = "PASS" ] &&
[ "$OBSERVABILITY_STATUS" = "PASS" ] &&
[ "$FUNCTIONAL_BINARY_STATUS" = "PASS" ] &&
[ "$START_LINES" -eq 1 ] &&
[ "$APPLICATION_START_LINES" -eq 1 ] &&
[ "$FILE_OPEN_LINES" -eq 1 ] &&
[ "$STARTUP_ERROR_LINES" -eq 0 ] &&
[ "$HASH_RC" -eq 0 ] &&
[ "$TEE_EXIT_CODE" = "0" ] &&
[ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] &&
[ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] &&
[ "$ERROR_LINES" -eq 0 ] &&
[ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then
FUNCTIONAL_STATUS="PASS"
else
FUNCTIONAL_STATUS="NOT_DEMONSTRATED"
fi
if [ "$MODE_STATUS" = "PASS" ] &&
[ "$PROFILE_CONFIG_STATUS" = "PASS" ] &&
[ "$SEMANTIC_BINARY_STATUS" = "PASS" ]; then
SEMANTIC_STATUS="PASS"
elif [ "$SEMANTIC_BINARY_STATUS" = "FAIL" ]; then
SEMANTIC_STATUS="FAIL"
else
SEMANTIC_STATUS="NOT_DEMONSTRATED"
fi
printf '============================================================\n'
printf 'VALIDAZIONE S03-H3 - MINIMAL / TEST / DIAGNOSTIC\n'
printf '============================================================\n'
printf 'Run directory: %s\n' "$RUN_DIR"
printf 'CANONICAL_STRUCTURAL_COMPLETENESS_STATUS=%s\n' "$CANONICAL_STRUCTURAL_COMPLETENESS_STATUS"
printf 'MINIMAL_RUNTIME_CONFIG_STATUS=%s\n' "$MINIMAL_RUNTIME_CONFIG_STATUS"
printf 'FUNCTION_RESOLUTION_LINES=%s\n' "$FUNCTION_RESOLUTION_LINES"
printf 'LOGGER_SIGNAL_LINES=%s\n' "$LOGGER_SIGNAL_LINES"
printf 'LOGGER_COUNTER_LINES=%s\n' "$LOGGER_COUNTER_LINES"
printf 'LOGGER_TIME_LINES=%s\n' "$LOGGER_TIME_LINES"
printf 'LOGGER_CYCLE_TIME_LINES=%s\n' "$LOGGER_CYCLE_TIME_LINES"
printf 'LOGGER_SCALAR_A_LINES=%s\n' "$LOGGER_SCALAR_A_LINES"
printf 'LOGGER_SCALAR_B_LINES=%s\n' "$LOGGER_SCALAR_B_LINES"
printf 'LOGGER_VECTOR_LINES=%s\n' "$LOGGER_VECTOR_LINES"
printf 'PARSER_RC=%s\n' "$PARSER_RC"
printf 'HASH_VERIFICATION_STATUS=%s\n' "$( [ "$HASH_RC" -eq 0 ] && printf PASS || printf FAIL )"
printf '\n--- CONFIGURATION VERIFIER DETAIL ---\n'
if [ "$MODE" = "MINIMAL" ]; then
sed 's/^/MINIMAL_CANONICAL_VERIFIER_/' "$CONFIG_REPORT"
sed 's/^/MINIMAL_RUNTIME_VERIFIER_/' "$RUNTIME_CONFIG_REPORT"
else
cat -- "$CONFIG_REPORT"
fi
printf '\n--- BINARY PARSER DETAIL ---\n'
sed 's/^/BINARY_PARSER_/' "$BINARY_REPORT"
printf '\n--- HASH DETAIL ---\n'
cat -- "$HASH_REPORT"
printf '\n--- CONTRATTO S03-H3 ---\n'
emit_contract
if [ "$FUNCTIONAL_STATUS" != "PASS" ] || [ "$SEMANTIC_STATUS" != "PASS" ]; then
printf 'VALIDATION_RESULT=FUNCTIONAL_OR_SEMANTIC_NOT_DEMONSTRATED\n'
exit 20
fi
if [ "$SIGINT_LINES" -ge 1 ] &&
[ "$STOP_OK_LINES" -ge 1 ] &&
[ "$TERMINATED_LINES" -ge 1 ] &&
[ "$FATAL_ERROR_LINES" -eq 0 ] &&
[ "$OS_ERROR_LINES" -eq 0 ] &&
[ "$ERROR_LINES" -eq 0 ] &&
[ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then
printf 'VALIDATION_RESULT=PASS_CLEAN_SHUTDOWN\n'
exit 0
fi
KNOWN_SHUTDOWN_TOTAL=$((DOUBLE_STOP_LINES + ASYNC_CLOSE_LINES + MUTEX_DESTROY_LINES))
if [ "$SIGINT_LINES" -ge 1 ] &&
[ "$STOP_OK_LINES" -ge 1 ] &&
[ "$TERMINATED_LINES" -ge 1 ] &&
[ "$KNOWN_SHUTDOWN_TOTAL" -ge 1 ] &&
[ "$UNCLASSIFIED_FATAL_LINES" -eq 0 ] &&
[ "$UNCLASSIFIED_OS_ERROR_LINES" -eq 0 ] &&
[ "$ERROR_LINES" -eq 0 ] &&
[ "$INITIALISATION_ERROR_LINES" -eq 0 ]; then
printf 'VALIDATION_RESULT=PASS_KNOWN_SHUTDOWN_ANOMALY\n'
exit 10
fi
printf 'VALIDATION_RESULT=PASS_UNCLASSIFIED_SHUTDOWN_ANOMALY\n'
exit 11
@@ -0,0 +1,331 @@
#!/usr/bin/env python3
"""Fail-closed structural verifier for the S03-H3 MINIMAL profile."""
from __future__ import annotations
import argparse
import hashlib
import os
import re
import sys
from pathlib import Path
EXPECTED_TEST_SHA256 = (
"d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e"
)
PLACEHOLDER = "__MARTE_RUN_DIR__"
TEST_FUNCTIONS = (
" Functions = "
"{GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}"
)
MINIMAL_FUNCTIONS = (
" Functions = "
"{GAMTimer SentinelProducer IOGAM_Writer}"
)
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def read_utf8(path: Path) -> tuple[bytes, str]:
data = path.read_bytes()
return data, data.decode("utf-8")
def object_matches(text: str, name: str) -> list[re.Match[str]]:
pattern = re.compile(
rf"(?m)^[ \t]*\+{re.escape(name)}[ \t]*=[ \t]*\{{[ \t]*(?:\r?\n|$)"
)
return list(pattern.finditer(text))
def object_count(text: str, name: str) -> int:
return len(object_matches(text, name))
def block_span(text: str, name: str) -> tuple[int, int]:
matches = object_matches(text, name)
if len(matches) != 1:
raise ValueError(f"{name.upper()}_ANCHOR_COUNT_{len(matches)}")
match = matches[0]
opening = text.find("{", match.start(), match.end())
if opening < 0:
raise ValueError(f"{name.upper()}_OPENING_BRACE_MISSING")
depth = 0
quoted = False
escaped = False
closing = -1
for index in range(opening, len(text)):
char = text[index]
if quoted:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == '"':
quoted = False
continue
if char == '"':
quoted = True
elif char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
closing = index + 1
break
if depth < 0:
raise ValueError(f"{name.upper()}_UNBALANCED_BRACE")
if closing < 0:
raise ValueError(f"{name.upper()}_CLOSING_BRACE_MISSING")
if text.startswith("\r\n", closing):
closing += 2
elif text.startswith("\n", closing):
closing += 1
return match.start(), closing
def derive_expected_minimal(canonical: str) -> str:
if canonical.count(TEST_FUNCTIONS) != 1:
raise ValueError(
f"FUNCTIONS_ANCHOR_COUNT_{canonical.count(TEST_FUNCTIONS)}"
)
if canonical.count(MINIMAL_FUNCTIONS) != 0:
raise ValueError("SOURCE_ALREADY_PARTIALLY_TRANSFORMED")
spans = [
block_span(canonical, "GAMDisplay"),
block_span(canonical, "Display"),
]
if spans[0][0] < spans[1][1] and spans[1][0] < spans[0][1]:
raise ValueError("AUTHORIZED_BLOCK_SPANS_OVERLAP")
transformed = canonical
for start, end in sorted(spans, reverse=True):
transformed = transformed[:start] + transformed[end:]
transformed = transformed.replace(TEST_FUNCTIONS, MINIMAL_FUNCTIONS, 1)
return transformed
class Report:
def __init__(self) -> None:
self.values: list[tuple[str, str]] = []
self.failed = False
self.first_error = "NONE"
def value(self, name: str, value: object) -> None:
self.values.append((name, str(value)))
def status(self, name: str, passed: bool, error: str) -> None:
self.value(name, "PASS" if passed else "FAIL")
if not passed:
self.failed = True
if self.first_error == "NONE":
self.first_error = error
def emit(self) -> int:
for name, value in self.values:
print(f"{name}={value}")
print(f"MINIMAL_VERIFIER_ERROR={self.first_error}")
return 1 if self.failed else 0
def emit_canonical_failure(error: str) -> int:
report = Report()
report.value("VERIFICATION_MODE", "CANONICAL")
report.value("GAMDisplay_OBJECT_COUNT", 0)
report.value("Display_DATASOURCE_COUNT", 0)
report.value("GAMDisplay_THREAD_TOKEN_COUNT", 0)
report.value("GAMTimer_OBJECT_COUNT", 0)
report.value("SentinelProducer_OBJECT_COUNT", 0)
report.value("IOGAM_Writer_OBJECT_COUNT", 0)
report.value("FileWriter_OBJECT_COUNT", 0)
report.status("THREAD_FUNCTIONS_STATUS", False, error)
report.status("BINARY_PATH_STRUCTURE_STATUS", False, error)
report.status("CANONICAL_STRUCTURAL_COMPLETENESS_STATUS", False, error)
report.status("PROFILE_CONFIG_STATUS", False, error)
return report.emit()
def verify_canonical(canonical_path: Path, minimal_path: Path) -> int:
try:
canonical_bytes, canonical = read_utf8(canonical_path)
minimal_bytes, minimal = read_utf8(minimal_path)
except (OSError, UnicodeError) as exc:
return emit_canonical_failure(f"INPUT_READ_FAILED_{type(exc).__name__}")
canonical_hash = sha256_bytes(canonical_bytes)
try:
expected_minimal = derive_expected_minimal(canonical)
except ValueError as exc:
return emit_canonical_failure(str(exc))
expected_bytes = expected_minimal.encode("utf-8")
complete = minimal_bytes == expected_bytes
report = Report()
report.value("VERIFICATION_MODE", "CANONICAL")
report.value("CANONICAL_TEST_SHA256", canonical_hash)
report.value("EXPECTED_CANONICAL_TEST_SHA256", EXPECTED_TEST_SHA256)
report.value("MINIMAL_CONFIG_SHA256", sha256_bytes(minimal_bytes))
report.value("EXPECTED_MINIMAL_CONFIG_SHA256", sha256_bytes(expected_bytes))
counts = {
"GAMDisplay_OBJECT_COUNT": object_count(minimal, "GAMDisplay"),
"Display_DATASOURCE_COUNT": object_count(minimal, "Display"),
"GAMDisplay_THREAD_TOKEN_COUNT": len(
re.findall(r"\bGAMDisplay\b", minimal)
),
"GAMTimer_OBJECT_COUNT": object_count(minimal, "GAMTimer"),
"SentinelProducer_OBJECT_COUNT": object_count(
minimal, "SentinelProducer"
),
"IOGAM_Writer_OBJECT_COUNT": object_count(minimal, "IOGAM_Writer"),
"FileWriter_OBJECT_COUNT": object_count(minimal, "FileWriter"),
}
for name, value in counts.items():
report.value(name, value)
expected_counts = {
"GAMDisplay_OBJECT_COUNT": 0,
"Display_DATASOURCE_COUNT": 0,
"GAMDisplay_THREAD_TOKEN_COUNT": 0,
"GAMTimer_OBJECT_COUNT": 1,
"SentinelProducer_OBJECT_COUNT": 1,
"IOGAM_Writer_OBJECT_COUNT": 1,
"FileWriter_OBJECT_COUNT": 1,
}
counts_pass = counts == expected_counts
thread_pass = minimal.count(MINIMAL_FUNCTIONS) == 1
binary_pass = (
counts["IOGAM_Writer_OBJECT_COUNT"] == 1
and counts["FileWriter_OBJECT_COUNT"] == 1
and minimal.count('Filename = "__MARTE_RUN_DIR__/s03_output.bin"') == 1
and minimal.count('FileFormat = "binary"') == 1
and minimal.count("DataSource = FileWriter") == 6
)
parent_pass = canonical_hash == EXPECTED_TEST_SHA256
report.value("UNAUTHORIZED_ADDITION_COUNT", 0 if complete else 1)
report.status(
"CANONICAL_TEST_IDENTITY_STATUS",
parent_pass,
"CANONICAL_TEST_SHA256_MISMATCH",
)
report.status(
"AUTHORIZED_ABSENCE_AND_PRESENCE_STATUS",
counts_pass,
"OBJECT_OR_TOKEN_COUNT_MISMATCH",
)
report.status(
"THREAD_FUNCTIONS_STATUS", thread_pass, "THREAD_FUNCTIONS_MISMATCH"
)
report.status(
"BINARY_PATH_STRUCTURE_STATUS",
binary_pass,
"BINARY_PATH_STRUCTURE_MISMATCH",
)
report.status(
"CANONICAL_STRUCTURAL_COMPLETENESS_STATUS",
parent_pass and complete,
"UNAUTHORIZED_STRUCTURAL_DIFFERENCE",
)
report.status(
"PROFILE_CONFIG_STATUS",
parent_pass and complete and counts_pass and thread_pass and binary_pass,
"MINIMAL_PROFILE_CONFIG_NOT_PROVEN",
)
return report.emit()
def extract_runtime_filename(text: str) -> list[str]:
return re.findall(r'(?m)^[ \t]*Filename[ \t]*=[ \t]*"([^"]+)"[ \t]*$', text)
def verify_runtime(packaged_path: Path, runtime_path: Path, run_dir: Path) -> int:
report = Report()
report.value("VERIFICATION_MODE", "RUNTIME")
try:
packaged_bytes, packaged = read_utf8(packaged_path)
runtime_bytes, runtime = read_utf8(runtime_path)
except (OSError, UnicodeError) as exc:
report.status(
"MINIMAL_RUNTIME_CONFIG_STATUS",
False,
f"INPUT_READ_FAILED_{type(exc).__name__}",
)
return report.emit()
supplied = str(run_dir)
absolute = run_dir.is_absolute()
exists = run_dir.is_dir()
placeholder_count = packaged.count(PLACEHOLDER)
unresolved_count = runtime.count(PLACEHOLDER)
expected_runtime = packaged.replace(PLACEHOLDER, supplied)
exact = runtime_bytes == expected_runtime.encode("utf-8")
filenames = extract_runtime_filename(runtime)
expected_filename = os.path.join(supplied, "s03_output.bin")
filename_pass = filenames.count(expected_filename) == 1 and len(filenames) == 1
report.value("PACKAGED_MINIMAL_SHA256", sha256_bytes(packaged_bytes))
report.value("RUNTIME_CONFIG_SHA256", sha256_bytes(runtime_bytes))
report.value("PACKAGED_PLACEHOLDER_COUNT", placeholder_count)
report.value("RUNTIME_UNRESOLVED_PLACEHOLDER_COUNT", unresolved_count)
report.value("RUNTIME_FILENAME", filenames[0] if len(filenames) == 1 else "AMBIGUOUS")
report.value("EXPECTED_RUNTIME_FILENAME", expected_filename)
report.status("RUN_DIR_ABSOLUTE_STATUS", absolute, "RUN_DIR_NOT_ABSOLUTE")
report.status("RUN_DIR_EXISTS_STATUS", exists, "RUN_DIR_NOT_FOUND")
report.status(
"PACKAGED_PLACEHOLDER_STATUS",
placeholder_count == 1,
"PACKAGED_PLACEHOLDER_COUNT_MISMATCH",
)
report.status(
"RUNTIME_PLACEHOLDER_RESOLUTION_STATUS",
unresolved_count == 0,
"RUNTIME_PLACEHOLDER_UNRESOLVED",
)
report.status(
"RUNTIME_FILENAME_STATUS", filename_pass, "RUNTIME_FILENAME_OUTSIDE_RUN_DIR"
)
report.status(
"MINIMAL_RUNTIME_CONFIG_STATUS",
absolute
and exists
and placeholder_count == 1
and unresolved_count == 0
and filename_pass
and exact,
"RUNTIME_DIFF_EXCEEDS_AUTHORIZED_SUBSTITUTION",
)
return report.emit()
def main() -> int:
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="mode", required=True)
canonical = subparsers.add_parser("CANONICAL")
canonical.add_argument("canonical_test", type=Path)
canonical.add_argument("packaged_minimal", type=Path)
runtime = subparsers.add_parser("RUNTIME")
runtime.add_argument("packaged_minimal", type=Path)
runtime.add_argument("runtime_configuration", type=Path)
runtime.add_argument("run_dir", type=Path)
args = parser.parse_args()
if args.mode == "CANONICAL":
return verify_canonical(args.canonical_test, args.packaged_minimal)
return verify_runtime(
args.packaged_minimal, args.runtime_configuration, args.run_dir
)
if __name__ == "__main__":
sys.exit(main())