S95 · SOURCE-BOUND GATE EVIDENCE
G8h dormant runtime source/object GREEN; S96 wiring sırada
Operations --test hedefi → focused test içindeki include_str!/#[path] bağı → kaynak kesiti Bu sayfa yalnız S95 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S95Focused kod testiOperations id exactsource SHA exacttest target exact
operation: rpi5-g8h-dormant-bounded-runtime-source-green
uygulama/model · focused test · Operations · 3 exact excerpt
sequence-bound=true · implementation-bound=true
01 · Testin bağlı olduğu uygulama/model kodu
Kapının yürüttüğü gerçek kaynak
tam Rust öğesiL1379–L1764
kernel/src/rpi5_g8h.rs::rpi5_g8h_try_handle_secondary_irq
/// Handle one CPU1-owned PPI27 and return the exact frame that exceptions.S
/// must restore. `None` means not owned; `Some(ctx)` means handled without a
/// switch; `Some(other)` is an EOI-complete task redirect.
#[no_mangle]
#[inline(never)]
pub fn rpi5_g8h_try_handle_secondary_irq(
ctx: &mut ExceptionContext,
ack: u32,
int_id: u32,
) -> Option<*mut ExceptionContext> {
if !rpi5_g8h_secondary_irq_active() {
return None;
}
let ctx_ptr = ctx as *mut ExceptionContext;
let tpidr = read_tpidr();
if tpidr != TARGET_CPU_INDEX {
return consume_owned_irq_error(ctx, ack, int_id, ERR_TPIDR, TARGET_CPU_INDEX, tpidr);
}
let ack_intid = gic::interrupt_id(ack);
if int_id != ack_intid {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_ACK_RAW,
int_id as u64,
ack_intid as u64,
);
}
if int_id != TIMER_PPI {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_IRQ_ID,
TIMER_PPI as u64,
int_id as u64,
);
}
if ack != TIMER_PPI {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_ACK_RAW,
TIMER_PPI as u64,
ack as u64,
);
}
if ctx.spsr_el1 & SPSR_IRQ_MASK != 0
|| ctx.spsr_el1 & SPSR_DAF_MASK != SPSR_DAF_MASK
|| ctx.spsr_el1 & SPSR_MODE_MASK != SPSR_EL1H_MODE
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_DAIF,
SPSR_EL1H_IRQ_UNMASKED,
ctx.spsr_el1,
);
}
let prior_switches = SWITCHES.load(Ordering::Acquire);
let expected_stage = match stage_for_switches(prior_switches) {
Some(value) if value != STAGE_SWITCH4 => value,
_ => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_IRQ_STAGE,
STAGE_SWITCH4,
STAGE.load(Ordering::Relaxed),
)
}
};
let stage = STAGE.load(Ordering::Acquire);
if stage != expected_stage {
return consume_owned_irq_error(ctx, ack, int_id, ERR_IRQ_STAGE, expected_stage, stage);
}
let current = CURRENT.load(Ordering::Acquire);
let expected_current = if prior_switches & 1 == 0 {
TASK_A
} else {
TASK_B
};
if current != expected_current
|| !frame_in_stack(
ctx_ptr as u64,
task_stack_bounds(current).0 as u64,
task_stack_bounds(current).1 as u64,
)
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_FRAME_OWNER,
expected_current,
ctx_ptr as u64,
);
}
if current == TASK_A
&& (ctx_ptr as u64).checked_add(TRAP_FRAME_BYTES as u64)
!= Some(TASK_A_ENTRY_SP.load(Ordering::Acquire))
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_FRAME_STABILITY,
TASK_A_ENTRY_SP.load(Ordering::Relaxed),
ctx_ptr as u64,
);
}
let expected_prior = IRQ_DELIVERIES.load(Ordering::Acquire);
if expected_prior >= EXPECTED_CPU1_DELIVERIES
|| IRQ_ACKS.load(Ordering::Acquire) != expected_prior
|| IRQ_EOIS.load(Ordering::Acquire) != expected_prior
|| DEADLINE_ADVANCES.load(Ordering::Acquire) != expected_prior
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_IRQ_COUNTS,
expected_prior,
IRQ_ACKS.load(Ordering::Relaxed),
);
}
let delivery = match expected_prior.checked_add(1) {
Some(value) => value,
None => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_ARITHMETIC,
expected_prior,
1,
)
}
};
let before = match timer::rpi5_g8g_periodic_snapshot(SLOT_CPU1) {
Ok(snapshot) => snapshot,
Err(_) => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_SNAPSHOT,
SLOT_CPU1 as u64,
0,
)
}
};
let expected_current_cval = match expected_deadline(before.start_count, before.period, delivery)
{
Some(value) => value,
None => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_ARITHMETIC,
before.start_count,
delivery,
)
}
};
let next_delivery = match delivery.checked_add(1) {
Some(value) => value,
None => {
return consume_owned_irq_error(ctx, ack, int_id, ERR_TIMER_ARITHMETIC, delivery, 1)
}
};
let expected_next = match expected_deadline(before.start_count, before.period, next_delivery) {
Some(value) => value,
None => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_ARITHMETIC,
before.start_count,
next_delivery,
)
}
};
let fired_count = timer::read_count();
if before.ticks != expected_prior
|| before.next_cval != expected_current_cval
|| fired_count < before.next_cval
|| fired_count >= expected_next
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_CVAL,
expected_current_cval,
before.next_cval,
);
}
let drift = match fired_count.checked_sub(before.next_cval) {
Some(value) => value,
None => {
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_ARITHMETIC,
before.next_cval,
fired_count,
)
}
};
let after = match timer::rpi5_g8g_advance_local_periodic(SLOT_CPU1) {
Ok(snapshot) => snapshot,
Err(_) => {
return consume_owned_irq_error(ctx, ack, int_id, ERR_TIMER_ADVANCE, expected_next, 0)
}
};
if after.start_count != before.start_count
|| after.period != before.period
|| after.next_cval != expected_next
|| after.ticks != delivery
{
return consume_owned_irq_error(
ctx,
ack,
int_id,
ERR_TIMER_ADVANCE,
expected_next,
after.next_cval,
);
}
let final_ctl = if delivery == EXPECTED_CPU1_DELIVERIES {
match timer::rpi5_g8g_disable_local_timer(SLOT_CPU1) {
Ok(control) => control,
Err(_) => return consume_owned_irq_error(ctx, ack, int_id, ERR_TIMER_DISABLE, 0b10, 0),
}
} else {
0
};
gic::end_of_interrupt(ack);
completion_barrier();
if let Err(code) = store_outgoing_frame(current, ctx_ptr) {
return fail_owned_after_eoi(ctx, code, target_frame_for(current), ctx_ptr as u64);
}
IRQ_DELIVERIES.store(delivery, Ordering::Relaxed);
IRQ_ACKS.store(delivery, Ordering::Relaxed);
IRQ_EOIS.store(delivery, Ordering::Relaxed);
DEADLINE_ADVANCES.store(delivery, Ordering::Relaxed);
let is_switch_delivery = delivery == EXPECTED_SWITCH_LOCAL_TICKS[0]
|| delivery == EXPECTED_SWITCH_LOCAL_TICKS[1]
|| delivery == EXPECTED_SWITCH_LOCAL_TICKS[2]
|| delivery == EXPECTED_SWITCH_LOCAL_TICKS[3];
if !is_switch_delivery {
return Some(ctx_ptr);
}
let switch_index = prior_switches as usize;
if switch_index >= REQUIRED_SWITCHES as usize
|| EXPECTED_SWITCH_LOCAL_TICKS[switch_index] != delivery
{
return fail_owned_after_eoi(ctx, ERR_SWITCH_ORDER, prior_switches, delivery);
}
let progress_sample = TASK_PROGRESS.load(Ordering::Acquire);
let (expected_progress, expected_a_segments, expected_b_segments) = match switch_index {
0 => (TASK_PROGRESS_A1, TASK_SEGMENT_ONE, 0),
1 => (TASK_PROGRESS_B1, TASK_SEGMENT_ONE, TASK_SEGMENT_ONE),
2 => (TASK_PROGRESS_A2, TASK_SEGMENT_TWO, TASK_SEGMENT_ONE),
3 => (TASK_PROGRESS_B2, TASK_SEGMENT_TWO, TASK_SEGMENT_TWO),
_ => return fail_owned_after_eoi(ctx, ERR_SWITCH_ORDER, 3, switch_index as u64),
};
if progress_sample != expected_progress
|| TASK_A_RUN_SEGMENTS.load(Ordering::Acquire) != expected_a_segments
|| TASK_B_RUN_SEGMENTS.load(Ordering::Acquire) != expected_b_segments
{
return fail_owned_after_eoi(ctx, ERR_PROGRESS, expected_progress, progress_sample);
}
let next = if current == TASK_A { TASK_B } else { TASK_A };
let target_frame = target_frame_for(next);
if !task_frame_contains(next, target_frame) {
return fail_owned_after_eoi(ctx, ERR_TARGET_FRAME, next, target_frame);
}
let target = target_frame as *mut ExceptionContext;
let target_spsr = unsafe { (*target).spsr_el1 };
if target_spsr & SPSR_TASK_MASK != SPSR_EL1H_IRQ_UNMASKED {
return fail_owned_after_eoi(ctx, ERR_DAIF, SPSR_EL1H_IRQ_UNMASKED, target_spsr);
}
if delivery == EXPECTED_CPU1_DELIVERIES {
unsafe {
(*target).spsr_el1 |= SPSR_IRQ_MASK;
}
if IRQ_HANDLER_ACTIVE
.compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
return fail_owned_after_eoi(ctx, ERR_IRQ_OWNERSHIP, 1, 0);
}
}
let next_switch = match prior_switches.checked_add(1) {
Some(value) => value,
None => return fail_owned_after_eoi(ctx, ERR_TIMER_ARITHMETIC, prior_switches, 1),
};
let next_stage = match stage_for_switches(next_switch) {
Some(value) => value,
None => return fail_owned_after_eoi(ctx, ERR_SWITCH_ORDER, REQUIRED_SWITCHES, next_switch),
};
let evidence_progress = progress_sample;
SWITCH_TICKS[switch_index].store(delivery, Ordering::Relaxed);
SWITCH_ACK_RAW[switch_index].store(ack as u64, Ordering::Relaxed);
SWITCH_EOI_RAW[switch_index].store(ack as u64, Ordering::Relaxed);
SWITCH_PREV_CVAL[switch_index].store(before.next_cval, Ordering::Relaxed);
SWITCH_NEXT_CVAL[switch_index].store(after.next_cval, Ordering::Relaxed);
SWITCH_FIRED_COUNT[switch_index].store(fired_count, Ordering::Relaxed);
SWITCH_DRIFT_COUNTS[switch_index].store(drift, Ordering::Relaxed);
SWITCH_FROM_FRAME[switch_index].store(ctx_ptr as u64, Ordering::Relaxed);
SWITCH_TO_FRAME[switch_index].store(target_frame, Ordering::Relaxed);
SWITCH_PROGRESS[switch_index].store(evidence_progress, Ordering::Relaxed);
SWITCH_PROGRESS_SAMPLE[switch_index].store(progress_sample, Ordering::Relaxed);
if CURRENT
.compare_exchange(current, next, Ordering::AcqRel, Ordering::Acquire)
.is_err()
|| SWITCHES
.compare_exchange(
prior_switches,
next_switch,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
return fail_owned_after_eoi(
ctx,
ERR_SWITCH_ORDER,
current,
CURRENT.load(Ordering::Relaxed),
);
}
if delivery == EXPECTED_CPU1_DELIVERIES {
FINAL_TIMER_CTL.store(final_ctl, Ordering::Relaxed);
DISABLED_NEXT_CVAL.store(after.next_cval, Ordering::Relaxed);
if COMPLETION_TOKEN
.compare_exchange(
COMPLETION_EMPTY,
COMPLETION_READY,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
return fail_owned_after_eoi(
ctx,
ERR_COMPLETION,
COMPLETION_EMPTY,
COMPLETION_TOKEN.load(Ordering::Relaxed),
);
}
}
if STAGE
.compare_exchange(
expected_stage,
next_stage,
Ordering::Release,
Ordering::Acquire,
)
.is_err()
{
return fail_owned_after_eoi(
ctx,
ERR_IRQ_STAGE,
expected_stage,
STAGE.load(Ordering::Relaxed),
);
}
Some(target_frame as *mut ExceptionContext)
}snippet sha256: c60794da1b29…file sha256: 624b78efcdd3…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam Rust öğesiL1125–L1163
simulation/tests/rpi5_g8h_runtime_source.rs::make_recipes_freeze_s94_and_run_only_the_two_s95_host_gates
#[test]
fn make_recipes_freeze_s94_and_run_only_the_two_s95_host_gates() {
let contract_body = MAKEFILE
.split_once("verify-rpi5-g8h-contract:")
.expect("S94 contract target")
.1
.split_once("\n\n")
.expect("bounded S94 contract target")
.0;
assert_eq!(
contract_body,
concat!(
"\n\t$(CARGO) test -p aselsan_microkernel_simulation \\\n",
"\t\t--test rpi5_g8g_uart \\\n",
"\t\t--test rpi5_g8h_source --test rpi5_g8h_uart\n",
"\t$(CARGO) check -p aselsan_microkernel_simulation \\\n",
"\t\t--example verify_rpi5_g8h_log\n",
"\t$(CARGO) run -p aselsan_microkernel_simulation \\\n",
"\t\t--example verify_rpi5_g8h_log -- --canonical",
),
);
let runtime_body = MAKEFILE
.split_once("verify-rpi5-g8h-runtime:")
.expect("S95 runtime target")
.1
.split_once("\n\n")
.expect("bounded S95 runtime target")
.0;
assert_eq!(
runtime_body,
concat!(
" verify-rpi5-g8h-contract\n",
"\t$(CARGO) test -p aselsan_microkernel_simulation \\\n",
"\t\t--test rpi5_g8h_runtime_source --test rpi5_g8h_runtime_compile",
),
);
assert!(CONTRACT.contains("Allocation-free contract"));
}snippet sha256: f48bc26c7a56…file sha256: 307fdea01f10…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL28376–L28467
website/src/lib/operations.ts::rpi5-g8h-dormant-bounded-runtime-source-green
{
id: "rpi5-g8h-dormant-bounded-runtime-source-green",
date: "2026-08-22",
sequence: 95,
status: "verified",
title: "G8h dormant runtime source/object GREEN; S96 wiring sırada",
summary:
"Sequence 94'ün sentetik G8h contract/parser kabulü değiştirilmeden iki static 64 KiB EL1 task stack'i, exact state/current/completion CAS zinciri ve bounded CPU1 PPI27 preemption runtime'ı dormant kaynak olarak eklendi. Task A mevcut call-on-stack seam'iyle başlar; Task B fixed-SP AArch64 assembly island'ında çalışır. Task-owned 100/200/300/400 progress ile ABABA switch'leri local tick 10/20/30/40'ta oluşur; exact 40 delivery/ack/EOI/deadline advance sonunda tick-40 timer-off→EOI+barrier→saved-I→active-close→A redirect sırası korunur. CPU1 monitor SP exact restore ve bounded post-stop ardından PARKED olur; CPU0 bütün kanıtı yeniden doğrular. Actual no_std AArch64 opt1/z object kapısı ve recursive production-unreachability audit'i FINAL GO aldı. Bu yalnız dormant source/object kanıtıdır: S96 production wiring, production layout, image/package, microSD, UART, power ve fiziksel BOOT8H kapalıdır; Pi kapalı ve son fiziksel PASS S92 BOOT8G'dir.",
evidence: [
"Exact success state: IDLE→RELEASED→TASK_A→SWITCH1→SWITCH2→SWITCH3→SWITCH4→RETURNED→PARKED; first-winner payload publication'lı ERROR terminaldir.",
"İki ayrı static 64 KiB / 4 KiB-aligned stack, iki-word canary, integer context 264 B, trap frame 272 B/16-byte aligned ve gerçek 16 KiB CPU1 monitor stack ayrıklığı doğrulandı.",
"CPU1 identity exact MPIDR=0x100, TPIDR=1, EL1h/SPSel1, entry DAIF D/A/F+I masked ve CPACR FPEN=0; prior G8g slot1 ticks=2, timer off+masked, PPI27 pending/active=0/0.",
"Task A only-on-A-stack IRQ unmask ve armed-SP frame bağı; Task B fixed-SP assembly, full affinity/owner/stage/deadline kontrolleri ve accidental-return sentinel'ı ile kanıtlandı.",
"Task-owned progress 100/200/300/400; segment toplamları A=3/B=2; order ABABA ve switch local ticks 10/20/30/40 exact.",
"Exact totals delivery/ack/EOI/deadline-advance/local-tick=40/40/40/40/40; direct dispatch=1, dispatcher dispatch=0 ve return IRQ=0.",
"Tick 40 lifecycle exact timer off+masked → EOI → completion barrier → target A saved SPSR.I → active ownership close → evidence/state → frame redirect.",
"A completion token'ı tüketip monitor stack'e normal döndü; SP before/after exact, current LOCAL_IDLE, canary A/B OK ve B normal return=0 sözleşmesi source/object kapısında bağlıdır.",
"Bounded liveness: capture+200 period inclusive CPU1 start limiti, +bir period observation payı, published CPU1 start+200 period task deadline ve bir period'dan kısa post-stop penceresi.",
"CPU0 IrqGuard altında G8g third-check/count ve CPU0 timer/global TICKS prerequisite'lerini doğrular; final CPU0 state mutation=0 ve bütün arithmetic/frame/drift değerlerini yeniden hesaplar.",
"`make verify-rpi5-g8h-runtime` exact 43/43 PASS: historical G8g UART 11/11 + S94 source 5/5 + G8h UART 8/8 + runtime object 1/1 + runtime source 18/18; CLI check ve canonical run ayrıca PASS, physical=NO.",
"Serialized full simulation exact 288/288 PASS (41 result block); `cargo check --workspace`, fmt ve diff kapıları GREEN.",
"Actual AArch64 no_std object iki profil `-Copt-level=1/z` ile üretildi; whole-object FP/SIMD/SVE/SME/panic-edge, Task A unmasked CFG ve Task B fixed-SP taramaları PASS.",
"Recursive production dormancy audit: production reachability=ABSENT; main/G8g continuation/exception/timer/GIC/build/Cargo akışında G8h çağrısı veya module wiring yok.",
"Runtime exact 86.200 B / fa4fd192aaaa16256ee95ffa099b9723724db4106aeb6758fc404df0070f8433.",
"Runtime source test exact 37.909 B / 39ab53b633b7382ff2a2cc7872e67a6a71bae88bc29cb7eb2c43f40ef0853de5.",
"Runtime compile test exact 15.786 B / 3502f98171e7b7bddfa6c012e222e1e67f0ac999e1aa37c50c6cad073fd91c5a; compile harness 2.868 B / 840cde6bfd97bfa3cfb30a7e0b9e5b811a8a12bd9c929ff58094f9c73bb0061f.",
"Versioned S94 source test exact 9.231 B / d992067beb849e4e87d13759c63d5b05c9a1861f8749bf73f4ecf39cd0294ac8; Makefile 24.521 B / 854df5848acc5a9ba38b033c1b017ad7109e935872b16f646b0b7d1876952e46.",
"S94 contract/parser/fixture/UART test/CLI/lib identities unchanged; canonical sentetik proof hâlâ source=synthetic physical=NO.",
"S95 proof exact 275 satır / 12.545 B / db45fe4cd59ce139edfdf2dbd6b70452a6a957202f6e0f6e7421b1705d2cd921.",
"Bağımsız semantic/security audit state, timeout, frame, fixed-SP, tick-40 lifecycle, error ownership ve dormancy yüzeylerinin tamamına FINAL GO verdi.",
"Son fiziksel PASS değişmedi: S92 immutable 18.978 B / 4abf8bb1…1a2 BOOT8G raw; Pi kapalı, device/UART/power/BOOT8H=NOT_PERFORMED.",
],
terminalSessionsNote:
"Sequence 95 oturumları host-only dormant kaynak ve standalone AArch64 object denetimleridir; production ELF veya fiziksel UART/BOOT8H oturumu değildir.",
terminalSessions: [
{
id: "g8h-sequence95-runtime-source",
title: "Dormant runtime state/frame/timeout source TDD",
commandLines: [
"cargo test -p aselsan_microkernel_simulation --test rpi5_g8h_runtime_source -- --test-threads=1",
"verify recursive production unreachability and forbidden runtime surfaces",
],
outputLines: [
"runtime source=18/18 PASS",
"state=IDLE→RELEASED→TASK_A→SW1→SW2→SW3→SW4→RETURNED→PARKED · ERROR terminal",
"production reachability=ABSENT · UART/heap/scheduler/SGI/TLBI/FP=ABSENT",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8h-sequence95-object-and-regression",
title: "Actual AArch64 object ve tam host regresyonu",
commandLines: [
"make verify-rpi5-g8h-runtime",
"cargo test -p aselsan_microkernel_simulation -- --test-threads=1",
"cargo check --workspace && cargo fmt --all -- --check",
],
outputLines: [
"aggregate=43/43 PASS · CLI check/run=PASS · physical=NO",
"AArch64 opt1/z object=PASS · A CFG=PASS · B fixed-SP=PASS · FP/SIMD/panic=ABSENT",
"serialized simulation=288/288 PASS · result_blocks=41",
"workspace check=PASS · fmt=PASS · diff=PASS",
],
exitCode: 0,
outputMode: "complete",
},
{
id: "g8h-sequence95-independent-final-audit",
title: "Bağımsız semantic/dormancy final audit",
commandLines: [
"re-audit frozen S95 identities and task/frame/timer lifecycle",
"verify production, device, UART, power and physical gates remain closed",
],
outputLines: [
"semantic/security audit=FINAL GO",
"runtime=fa4fd192…f8433 · source_test=39ab53b6…53de5 · compile_test=3502f981…1c5a",
"production reachability=ABSENT · device/UART/power/BOOT8H=NOT_PERFORMED",
"S95=GREEN · next=S96_PRODUCTION_WIRING",
],
exitCode: 0,
outputMode: "complete",
},
],
limitations: [
"S95 yalnız dormant runtime source ve standalone actual AArch64 object kabulüdür; production kernel wiring veya fiziksel BOOT8H değildir.",
"S96 production wiring sıradaki ayrı kapıdır; production machine-code/layout, full matrix, reproducibility ve image/package daha sonra açılır.",
"Pi kapalı tutulur; microSD, UART descriptor/capture, power, raw/archive ve G8h promotion kapıları STOP'tur.",
"Generic SMP scheduler/runqueue, migration, load balancing, ASID/TLB shootdown, CPU2/CPU3, hotplug ve soak kapalıdır.",
"Production deployment dirty/untracked workspace ve stale 47d22c9 source etiketiyle yapılır; canlı artifact doğrulansa da Git-provider provenance kurulmuş sayılmaz.",
],
},snippet sha256: 9b8354ed9948…file sha256: 9726dbf00f84…
Focused test komutu
cargo test -p aselsan_microkernel_simulation --test rpi5_g8h_runtime_source -- --test-threads=1Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9