FAILURE MAP
← Case archive

FA-67186 / Railway interlocking logic / Open access

Signal replacement and stick control: approach release condition · case 01

An approach-released signal clears before the train arrives on the approach.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The approach release condition is dropped.

VERIFIED REPAIR

Clear approach-released signals only with the approach occupied.

Unsuccessful approach: Requiring the approach for automatic signals as well stops them clearing for the next train.

Case contract

Each step gives [section beyond occupied, route set, approach occupied]. An auto signal behaves as permanently routed. A signal shows danger whenever the section beyond is occupied; a signal that was clear when a train occupies that section latches a stick that keeps it at danger until the route is set afresh (off then on); auto signals drop the stick every step. Otherwise it clears when routed, not stuck, and for approach-released signals only with the approach occupied. Output the state per step.

Why this case matters

Interlocking logic decides whether trains may be given authority; a wrong decision at this point either grants unsafe movements or strands traffic.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    mode = x['mode']
    state = 'danger'
    stick = False
    prev_route = False
    out = []
    for occ, route, appr in x['steps']:
        if mode == 'auto':
            route = True
        if (route and not prev_route) or mode == 'auto':
            stick = False
        if occ:
            if state == 'clear':
                stick = True
            state = 'danger'
        elif route and not stick:
            state = 'clear'
        else:
            state = 'danger'
        prev_route = route
        out.append(state)
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 7', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, False, True], [False, False, True], [False, True, False], [False, False, True], [False, True, True], [False, True, True], [False, True, True], [True, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('boundary: second train behind on the same route', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 1', {'mode': 'auto', 'steps': [[False, True, False], [False, False, True], [False, True, False], [False, False, True], [False, False, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear']), ('control 4', {'mode': 'controlled', 'steps': [[False, False, False], [False, True, True], [True, True, True], [False, True, True], [False, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger']), ('control 10', {'mode': 'controlled', 'steps': [[False, True, False], [True, False, True], [False, False, True], [False, False, True], [False, False, False], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 33', {'mode': 'approach', 'steps': [[False, True, False], [True, False, True], [False, True, False], [False, False, True], [True, False, False], [False, False, False], [True, False, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 25', {'mode': 'auto', 'steps': [[False, False, True], [False, True, True], [False, False, True], [False, True, True], [False, True, False], [False, False, True], [False, True, True], [False, True, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear']), ('boundary: approach-released signal replaced by train', {'mode': 'approach', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 12', {'mode': 'controlled', 'steps': [[False, False, True], [False, True, True], [True, False, True], [False, False, False], [False, False, True], [True, True, True], [True, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('sampled regression 15', {'mode': 'approach', 'steps': [[False, True, True], [False, True, False], [False, True, True]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 52', {'mode': 'approach', 'steps': [[False, True, False], [True, True, False], [True, True, False], [True, True, False], [True, True, False], [False, True, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 47', {'mode': 'auto', 'steps': [[True, True, True], [False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, True, False], [False, True, True], [True, True, False], [False, False, False], [False, False, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'danger', 'clear', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('control 23', {'mode': 'controlled', 'steps': [[False, True, True], [False, True, False], [False, True, True], [True, True, False], [False, True, False]]}, ['clear', 'clear', 'clear', 'danger', 'danger']), ('sampled regression 26', {'mode': 'approach', 'steps': [[False, False, False], [False, True, True], [True, True, False], [True, False, True], [True, True, False], [True, False, True], [False, False, False], [False, True, True], [False, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'danger', 'danger']), ('control 29', {'mode': 'auto', 'steps': [[True, False, False], [False, False, False], [True, False, False], [False, False, True], [False, False, False], [False, True, True], [False, True, True], [False, False, False], [True, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'clear', 'clear', 'clear', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 78', {'mode': 'approach', 'steps': [[False, False, True], [False, True, True], [False, True, False], [False, True, False], [False, True, False], [True, True, True], [True, False, True], [True, True, True]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 65', {'mode': 'auto', 'steps': [[False, False, True], [False, False, False], [True, False, False], [False, False, False], [False, True, False], [True, False, False], [False, False, False], [False, False, False], [False, False, True]]}, ['clear', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'clear', 'clear']), ('boundary: controlled signal with a standing train', {'mode': 'controlled', 'steps': [[True, True, True], [False, True, True]]}, ['danger', 'clear']), ('control 34', {'mode': 'approach', 'steps': [[False, True, True], [False, False, False], [False, True, True], [False, False, False], [True, False, True], [False, False, True], [True, False, False], [True, False, True]]}, ['clear', 'danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 37', {'mode': 'approach', 'steps': [[False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, False, True], [False, False, False], [False, False, True], [False, False, True], [False, False, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 40', {'mode': 'auto', 'steps': [[False, True, True], [True, True, True], [False, True, True], [False, True, False], [True, False, True], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 74', {'mode': 'auto', 'steps': [[True, False, True], [False, False, True], [False, False, True], [True, True, False], [True, True, True], [False, True, False], [False, False, True]]}, ['danger', 'clear', 'clear', 'danger', 'danger', 'clear', 'clear']), ('boundary: route re-set after a train', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, False, True], [False, True, True]]}, ['clear', 'danger', 'danger', 'clear']), ('sampled regression 45', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, True, False], [False, True, False], [True, True, False], [False, True, False], [False, True, False], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 48', {'mode': 'approach', 'steps': [[True, True, False], [False, True, True], [False, False, False], [False, False, True], [False, True, True], [True, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'clear', 'danger']), ('control 51', {'mode': 'controlled', 'steps': [[False, False, True], [False, False, False], [False, True, False], [False, True, True], [True, False, True], [False, False, True], [True, True, False]]}, ['danger', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: approach-released signal with empty approach['clear', 'clear']['danger', 'clear']Failed
boundary: automatic signal re-clears['clear', 'danger', 'clear']['clear', 'danger', 'clear']Passed
sampled regression 7['danger', 'danger', 'danger', 'danger', 'clear', 'danger', 'clear', 'clear', 'clear', 'danger']['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']Failed
boundary: occupied before clearing['danger', 'danger', 'clear']['danger', 'danger', 'clear']Passed
boundary: second train behind on the same route['clear', 'danger', 'danger']['clear', 'danger', 'danger']Passed
control 1['clear', 'clear', 'clear', 'clear', 'clear']['clear', 'clear', 'clear', 'clear', 'clear']Passed
control 4['danger', 'clear', 'danger', 'danger', 'danger']['danger', 'clear', 'danger', 'danger', 'danger']Passed
control 10['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']Passed

SHA-256 / 77409df949dc59510c3668fe8ef77cdd3cbfb9cde934646ad70e71dab012045b

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    mode = x['mode']
    state = 'danger'
    stick = False
    prev_route = False
    out = []
    for occ, route, appr in x['steps']:
        if mode == 'auto':
            route = True
        if (route and not prev_route) or mode == 'auto':
            stick = False
        if occ:
            if state == 'clear':
                stick = True
            state = 'danger'
        elif route and not stick and (mode == 'controlled' or appr):
            state = 'clear'
        else:
            state = 'danger'
        prev_route = route
        out.append(state)
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 7', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, False, True], [False, False, True], [False, True, False], [False, False, True], [False, True, True], [False, True, True], [False, True, True], [True, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('boundary: second train behind on the same route', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 1', {'mode': 'auto', 'steps': [[False, True, False], [False, False, True], [False, True, False], [False, False, True], [False, False, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear']), ('control 4', {'mode': 'controlled', 'steps': [[False, False, False], [False, True, True], [True, True, True], [False, True, True], [False, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger']), ('control 10', {'mode': 'controlled', 'steps': [[False, True, False], [True, False, True], [False, False, True], [False, False, True], [False, False, False], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 33', {'mode': 'approach', 'steps': [[False, True, False], [True, False, True], [False, True, False], [False, False, True], [True, False, False], [False, False, False], [True, False, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 25', {'mode': 'auto', 'steps': [[False, False, True], [False, True, True], [False, False, True], [False, True, True], [False, True, False], [False, False, True], [False, True, True], [False, True, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear']), ('boundary: approach-released signal replaced by train', {'mode': 'approach', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 12', {'mode': 'controlled', 'steps': [[False, False, True], [False, True, True], [True, False, True], [False, False, False], [False, False, True], [True, True, True], [True, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('sampled regression 15', {'mode': 'approach', 'steps': [[False, True, True], [False, True, False], [False, True, True]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 52', {'mode': 'approach', 'steps': [[False, True, False], [True, True, False], [True, True, False], [True, True, False], [True, True, False], [False, True, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 47', {'mode': 'auto', 'steps': [[True, True, True], [False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, True, False], [False, True, True], [True, True, False], [False, False, False], [False, False, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'danger', 'clear', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('control 23', {'mode': 'controlled', 'steps': [[False, True, True], [False, True, False], [False, True, True], [True, True, False], [False, True, False]]}, ['clear', 'clear', 'clear', 'danger', 'danger']), ('sampled regression 26', {'mode': 'approach', 'steps': [[False, False, False], [False, True, True], [True, True, False], [True, False, True], [True, True, False], [True, False, True], [False, False, False], [False, True, True], [False, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'danger', 'danger']), ('control 29', {'mode': 'auto', 'steps': [[True, False, False], [False, False, False], [True, False, False], [False, False, True], [False, False, False], [False, True, True], [False, True, True], [False, False, False], [True, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'clear', 'clear', 'clear', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 78', {'mode': 'approach', 'steps': [[False, False, True], [False, True, True], [False, True, False], [False, True, False], [False, True, False], [True, True, True], [True, False, True], [True, True, True]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 65', {'mode': 'auto', 'steps': [[False, False, True], [False, False, False], [True, False, False], [False, False, False], [False, True, False], [True, False, False], [False, False, False], [False, False, False], [False, False, True]]}, ['clear', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'clear', 'clear']), ('boundary: controlled signal with a standing train', {'mode': 'controlled', 'steps': [[True, True, True], [False, True, True]]}, ['danger', 'clear']), ('control 34', {'mode': 'approach', 'steps': [[False, True, True], [False, False, False], [False, True, True], [False, False, False], [True, False, True], [False, False, True], [True, False, False], [True, False, True]]}, ['clear', 'danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 37', {'mode': 'approach', 'steps': [[False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, False, True], [False, False, False], [False, False, True], [False, False, True], [False, False, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 40', {'mode': 'auto', 'steps': [[False, True, True], [True, True, True], [False, True, True], [False, True, False], [True, False, True], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 74', {'mode': 'auto', 'steps': [[True, False, True], [False, False, True], [False, False, True], [True, True, False], [True, True, True], [False, True, False], [False, False, True]]}, ['danger', 'clear', 'clear', 'danger', 'danger', 'clear', 'clear']), ('boundary: route re-set after a train', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, False, True], [False, True, True]]}, ['clear', 'danger', 'danger', 'clear']), ('sampled regression 45', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, True, False], [False, True, False], [True, True, False], [False, True, False], [False, True, False], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 48', {'mode': 'approach', 'steps': [[True, True, False], [False, True, True], [False, False, False], [False, False, True], [False, True, True], [True, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'clear', 'danger']), ('control 51', {'mode': 'controlled', 'steps': [[False, False, True], [False, False, False], [False, True, False], [False, True, True], [True, False, True], [False, False, True], [True, True, False]]}, ['danger', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: approach-released signal with empty approach['danger', 'clear']['danger', 'clear']Passed
boundary: automatic signal re-clears['danger', 'danger', 'danger']['clear', 'danger', 'clear']Failed
sampled regression 7['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']Passed
boundary: occupied before clearing['danger', 'danger', 'danger']['danger', 'danger', 'clear']Failed
boundary: second train behind on the same route['clear', 'danger', 'danger']['clear', 'danger', 'danger']Passed
control 1['danger', 'clear', 'danger', 'clear', 'danger']['clear', 'clear', 'clear', 'clear', 'clear']Failed
control 4['danger', 'clear', 'danger', 'danger', 'danger']['danger', 'clear', 'danger', 'danger', 'danger']Passed
control 10['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']Passed

SHA-256 / 94e3cf4faa4ce59b9a0354e0a58c0a6d1767907ed87a6cc848181e1d2e3ba09a

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    mode = x['mode']
    state = 'danger'
    stick = False
    prev_route = False
    out = []
    for occ, route, appr in x['steps']:
        if mode == 'auto':
            route = True
        if (route and not prev_route) or mode == 'auto':
            stick = False
        if occ:
            if state == 'clear':
                stick = True
            state = 'danger'
        elif route and not stick and (mode != 'approach' or appr):
            state = 'clear'
        else:
            state = 'danger'
        prev_route = route
        out.append(state)
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 7', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, False, True], [False, False, True], [False, True, False], [False, False, True], [False, True, True], [False, True, True], [False, True, True], [True, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('boundary: second train behind on the same route', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 1', {'mode': 'auto', 'steps': [[False, True, False], [False, False, True], [False, True, False], [False, False, True], [False, False, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear']), ('control 4', {'mode': 'controlled', 'steps': [[False, False, False], [False, True, True], [True, True, True], [False, True, True], [False, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger']), ('control 10', {'mode': 'controlled', 'steps': [[False, True, False], [True, False, True], [False, False, True], [False, False, True], [False, False, False], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 33', {'mode': 'approach', 'steps': [[False, True, False], [True, False, True], [False, True, False], [False, False, True], [True, False, False], [False, False, False], [True, False, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 25', {'mode': 'auto', 'steps': [[False, False, True], [False, True, True], [False, False, True], [False, True, True], [False, True, False], [False, False, True], [False, True, True], [False, True, False]]}, ['clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear', 'clear']), ('boundary: approach-released signal replaced by train', {'mode': 'approach', 'steps': [[False, True, True], [True, True, True], [False, True, True]]}, ['clear', 'danger', 'danger']), ('control 12', {'mode': 'controlled', 'steps': [[False, False, True], [False, True, True], [True, False, True], [False, False, False], [False, False, True], [True, True, True], [True, False, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('sampled regression 15', {'mode': 'approach', 'steps': [[False, True, True], [False, True, False], [False, True, True]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 52', {'mode': 'approach', 'steps': [[False, True, False], [True, True, False], [True, True, False], [True, True, False], [True, True, False], [False, True, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 47', {'mode': 'auto', 'steps': [[True, True, True], [False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, True, False], [False, True, True], [True, True, False], [False, False, False], [False, False, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'danger', 'clear', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('control 23', {'mode': 'controlled', 'steps': [[False, True, True], [False, True, False], [False, True, True], [True, True, False], [False, True, False]]}, ['clear', 'clear', 'clear', 'danger', 'danger']), ('sampled regression 26', {'mode': 'approach', 'steps': [[False, False, False], [False, True, True], [True, True, False], [True, False, True], [True, True, False], [True, False, True], [False, False, False], [False, True, True], [False, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'danger', 'danger']), ('control 29', {'mode': 'auto', 'steps': [[True, False, False], [False, False, False], [True, False, False], [False, False, True], [False, False, False], [False, True, True], [False, True, True], [False, False, False], [True, False, True], [False, True, False]]}, ['danger', 'clear', 'danger', 'clear', 'clear', 'clear', 'clear', 'clear', 'danger', 'clear'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: occupied before clearing', {'mode': 'auto', 'steps': [[True, False, False], [True, False, False], [False, False, False]]}, ['danger', 'danger', 'clear']), ('sampled regression 78', {'mode': 'approach', 'steps': [[False, False, True], [False, True, True], [False, True, False], [False, True, False], [False, True, False], [True, True, True], [True, False, True], [True, True, True]]}, ['danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 65', {'mode': 'auto', 'steps': [[False, False, True], [False, False, False], [True, False, False], [False, False, False], [False, True, False], [True, False, False], [False, False, False], [False, False, False], [False, False, True]]}, ['clear', 'clear', 'danger', 'clear', 'clear', 'danger', 'clear', 'clear', 'clear']), ('boundary: controlled signal with a standing train', {'mode': 'controlled', 'steps': [[True, True, True], [False, True, True]]}, ['danger', 'clear']), ('control 34', {'mode': 'approach', 'steps': [[False, True, True], [False, False, False], [False, True, True], [False, False, False], [True, False, True], [False, False, True], [True, False, False], [True, False, True]]}, ['clear', 'danger', 'clear', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 37', {'mode': 'approach', 'steps': [[False, False, True], [True, False, False], [False, False, True], [False, False, True], [True, False, True], [False, False, False], [False, False, True], [False, False, True], [False, False, False]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']), ('control 40', {'mode': 'auto', 'steps': [[False, True, True], [True, True, True], [False, True, True], [False, True, False], [True, False, True], [True, False, True], [True, False, True]]}, ['clear', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])], [('regression: approach-released signal with empty approach', {'mode': 'approach', 'steps': [[False, True, False], [False, True, True]]}, ['danger', 'clear']), ('boundary: automatic signal re-clears', {'mode': 'auto', 'steps': [[False, False, False], [True, False, True], [False, False, False]]}, ['clear', 'danger', 'clear']), ('sampled regression 18', {'mode': 'approach', 'steps': [[False, True, False], [False, False, False], [False, False, False], [True, True, False], [False, True, False], [True, True, True], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 74', {'mode': 'auto', 'steps': [[True, False, True], [False, False, True], [False, False, True], [True, True, False], [True, True, True], [False, True, False], [False, False, True]]}, ['danger', 'clear', 'clear', 'danger', 'danger', 'clear', 'clear']), ('boundary: route re-set after a train', {'mode': 'controlled', 'steps': [[False, True, True], [True, True, True], [False, False, True], [False, True, True]]}, ['clear', 'danger', 'danger', 'clear']), ('sampled regression 45', {'mode': 'approach', 'steps': [[False, False, False], [False, False, False], [False, True, False], [False, True, False], [True, True, False], [False, True, False], [False, True, False], [False, True, True]]}, ['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear']), ('control 48', {'mode': 'approach', 'steps': [[True, True, False], [False, True, True], [False, False, False], [False, False, True], [False, True, True], [True, True, False]]}, ['danger', 'clear', 'danger', 'danger', 'clear', 'danger']), ('control 51', {'mode': 'controlled', 'steps': [[False, False, True], [False, False, False], [False, True, False], [False, True, True], [True, False, True], [False, False, True], [True, True, False]]}, ['danger', 'danger', 'clear', 'clear', 'danger', 'danger', 'danger'])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: approach-released signal with empty approach['danger', 'clear']['danger', 'clear']Passed
boundary: automatic signal re-clears['clear', 'danger', 'clear']['clear', 'danger', 'clear']Passed
sampled regression 7['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']['danger', 'danger', 'danger', 'danger', 'danger', 'danger', 'clear', 'clear', 'clear', 'danger']Passed
boundary: occupied before clearing['danger', 'danger', 'clear']['danger', 'danger', 'clear']Passed
boundary: second train behind on the same route['clear', 'danger', 'danger']['clear', 'danger', 'danger']Passed
control 1['clear', 'clear', 'clear', 'clear', 'clear']['clear', 'clear', 'clear', 'clear', 'clear']Passed
control 4['danger', 'clear', 'danger', 'danger', 'danger']['danger', 'clear', 'danger', 'danger', 'danger']Passed
control 10['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']['clear', 'danger', 'danger', 'danger', 'danger', 'danger', 'danger']Passed

SHA-256 / a65a30b79f7472d95a05aaa9c21a53c93f182ab50206eda2f69bbe27a27db54f

Verification & scope

Stipulated toy interlocking contract for a bounded teaching model; it makes no claim of conformance to any railway signalling standard and omits real safety cases. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:47:50.540545+00:00.

Case digest / 9342e1735d03150ddf948b6fcfc3799e014dcab3577b59c89c7188a51894aacc