Skip to main content

Open Items

Living tracker. Updated as work progresses. This is the single place to see what is decided, what is open, and what is next across the safety-analysis + code-coverage effort. Newest status at the top of each section.

Last updated: 2026-08-11 (machine relay-feedback DESCOPE, reversible — see below).

2026-08-11 — Machine relay-feedback monitoring DESCOPED (reversible), pending new machine HW: The machn relay read-back (feedback) path is now compiled out by default via the Kconfig option CONFIG_MACHN_RELAY_FEEDBACK (default n). Reason: a new machine-specific hardware revision + wiring diagram is incoming on which the divider feedback voltages cannot be read sanely. Relays are still driven unchanged; the series de-energize-to-safe stop (HFT=1) and the l0/l1 dual-core lockstep/arming path are unchanged. Full writeup, reversal steps, and safety-case impact: docs/design/relay_feedback_descope.md.

  • Descope banners added to FMEA §5, FMEDA §5 + MC-2/MC-3, HARA H-10/SG-5, SAFETY_REQUIREMENTS SR-SYS-05 (marked descoped / stale / reversible, pointing to the descope doc).
  • FMEDA numeric recompute (SAFETY-OWNER SIGN-OFF). Re-run the machine channel with MC-2 relay-weld DC 90 % → 0 % (λ_DU 1.5 → 15) and MC-3 (feedback divider) removed from the diagnostic; the current §5 SFF/PFH overstate machine-channel integrity while feedback is descoped. Do it with the pending real-part-number firm-up so it is done once.
  • RESIDUAL RISK / integrator action. A single welded/stuck relay contact is now latent (dangerous-undetected) until the next stop demand (the partner still stops the robot on demand — stop-on-demand is unaffected). Recommend the integrator run a periodic commanded-open proof test of the stop circuit (T1, FMEDA §1.2) while feedback is descoped. SG-5 / SR-SYS-05 "diagnosable stuck-on" limb now rests on the integrator element or feedback restoration.
  • Re-enable on new HW. Set CONFIG_MACHN_RELAY_FEEDBACK=y and re-verify the SENSE pin numbers, divider ratios, and RELAY_FEEDBACK_MS against the new wiring; then revert these descope banners and re-instate the DC credit.
  • Downstream consumers (follow-up). ros2/protective_stop_machine (MachineRelayStatus) and tools/hil/ can adopt the new relay_feedback_monitored /state.json flag to label relay status n/a; benign (fault=0/stop=0) until then.

Prior update: 2026-08-07 (C-hardening pass — OTA-lineage, USB-NCM, cold-recovery, peer-scaling validated on the deployed build).

2026-08-07 — C-hardening pass (OTA / USB-NCM / cold-recovery / peer-scaling), validated on 7867d4b:

  • OTA wrong-lineage rejection at upload (FIX). The HTTP-upload OTA path (handler_ota) now enforces the first-chunk project_name check (mirrors the fleet-pull path) so a machine refuses a remote image up front (HTTP 400, no write/reboot) instead of booting it and rolling back; the remote gained symmetric CONFIG_ML_OTA_REQUIRE_PROJECT="pstop_remote". PENDING_VERIFY rollback separately confirmed working (a wrong image cleanly rolls back).
  • USB-NCM stability VERIFIED (no bug). 25-min local soak running the safety bond over USB-NCM: sf_txdrv=0 (zero UNrecovered TX drops), transients absorbed by the ERR_IF retry, bond held. The retry fix handles USB-NCM.
  • Machine-reboot cold-recovery VERIFIED for a clean reboot. Traced repro: remote self-re-bonds in ~35 s (disco demote → DERP fallback → re-handshake). The earlier prolonged wedge was a chaotic reboot (cross-lineage image, now prevented by the OTA fix) + operator error, not a standalone firmware bug.
  • [~] Peer-scaling armor holds the safety function under load; minor churn observed (FOLLOW-UP). 60 pps DISCO storm (worst-case per-peer box-open) on a full 128-peer netmap for 5 min: zero STOPs, bond held (state 2), mismatch 0 — the design's bar met. But a few rebonds at the load transitions and a ~6% heartbeat reply-deficit under sustained load (cores ~60%) show the isolation isn't perfect; consider tightening ML_DISCO_OPENS_PER_PASS or heartbeat TX priority under safety-bond stress. Not a safety failure (no STOP).

2026-08-04 — heartbeat-timeout raised (max_missed 3 → 5, commit 01e4c10): The machn (safety-credited machine, HARA A-07) MACHN_MAX_MISSED_HEARTBEATS was raised 3 → 5 (advertised to remotes). The silent-remote STOP-detection timeout moved from 400 × 3 ≈ 1.2 s to 400 × 5 ≈ 2.0 s; the internal reaction budget (SR-SYS-01 / HARA A-05) moved from ≈ 1.3 s to ≈ 2.1 s. Rationale: ~90-min Tailscale control-plane re-syncs caused transient ~1.4 s RTT spikes on the machine bond that exceeded the old 1.2 s timeout → nuisance rebonds / false STOPs; the 2.0 s margin rides through them (a deliberate trade of detection latency for far fewer false safety stops; addresses HARA H-02). Safety-case docs updated: SYSTEM_DEFINITION §2, HARA A-05/A-10/SG-1/SG-2/D1/H-03, SAFETY_REQUIREMENTS SR-SYS-01/02 + SR-H-01 + header, RECONCILIATION R-03/R-08, PSTOP_SAFETY_DESIGN §5.6, PRODUCTION_NETWORKING, CLOCK_GUARD, TRACEABILITY, README.

  • FINDING — FTTI/PST budget lengthened (integrator action). The internal budget is now ≈ 2.1 s (was ≈ 1.3 s). PST is an unknown integrator input (A-05, OD-2), so no stated numeric FTTI is internally violated — but any vehicle previously sized against the 1.3 s budget must be re-confirmed against ≥ 2.1 s + braking. Short-PST vehicles are the ones at risk; this compounds the existing H-03 DERP dark-window concern.
  • max_missed = 5 now equals the validated-envelope ceiling [1,5] (SR-H-03 / ROS 2 validate_timing). No headroom remains to raise it further without widening the envelope (which would need re-validation).
  • ROS 2 node default reconciled to 5 (2026-08-05). Integrator decision: max_missed default = 5 in all cases. ROS 2 node param default (protective_stop_machine_params.yaml) 3 → 5, matching machn and the host runner; design doc + test configs (hil.toml, stop_reset_battery, config-floor) updated to the 2.0 s timeout. Default now sits at the validated-envelope ceiling [1,5] by design.

2026-08-02 — "do them in order" batch merged + verified:

  1. #1 — SR-H-04b machine clock-freeze guard + DU-1 GPIO re-verify DONE (1cfbd9a): host+machn get_time_cb freeze guard (clock_guard, 5117-check host test), and remote periodic E-stop GPIO pad-config re-verify → controlled clean reset (gpio_cfg_fault, SR-R-09). DU-1 CLOSED.
  2. #2 — DU-3 object-code diversity gate DONE (d96129c): verified Option-B diverse expressions survive the optimizer (distinct object code) + scripts/check_estop_diversity.sh CI guard. DU-3 CLOSED.
  3. #3 — production networking robustness DONE (1f7604e, docs/design/production_networking.md): same-LAN direct-path validated; DERP-home + promote-direct-after-DISCO shipped.
  4. Stability battery (~30 min) PASSED: power-cycle 6/6, arm/press/discordance 3/3, 4-min soak, pstop_multi_remote_test 34/34, real 2-chip E2E.
  5. tools/stop_reset_battery.py FIXED (b802c35, 7c72e36): self-contained auto-spawn + strictly-monotonic send stamp; 22/22. Both defects were test-harness footguns, not firmware. See memory reference_pstop_battery_tool_footguns.

2026-08-02 — three-subagent batch merged (347de6f, on GitHub):

  1. DERP reachability FIXED in firmware (ml_wg_mgr.c): the chip was installing an unvalidated netmap endpoint as the live WG endpoint; now DERP-homed, direct only after bidirectional DISCO validation. Remote DUT reachable + full HIL smoke (arm / press→STOP / discordance) passed over the fixed safety link — this also closes the deferred firmware-extraction HIL validation. No infra workaround needed.
  2. SR-H-04 / DU-2 CLOSED: ESP32 TWDT/IWDT/RTC-WDT + dual-core mutual clock cross-check (docs/safety/watchdog_and_clock_cross_check.md); fault-injection proved a frozen clock is caught by the cross-check (missed by the lockstep memcmp).
  3. Quantitative FMEDA (docs/safety/fmeda.md): SFF≈93% / PFH≈3.7e-9/h / β=10%, SIL 3 met-but-fragile; closing DU-2 (done) + DU-1 → SFF≥99%. Escalated to user: real ESP32-S3 + relay part numbers, and demand rate
    • proof-test interval, to move from assumed to demonstrated.

1. Decisions log (settled)​

DateDecisionNotes
2026-08-02Firmware coverage = HYBRID only (host logic-harness for branch+MC/DC; on-target = functional/HIL smoke)On-target gcov needs JTAG+OpenOCD (ESP-IDF), but both JTAG routes are blocked by the design: the physical JTAG pins GPIO39–42 are the E-stop loop pins (main.c:207), and USB-Serial-JTAG is displaced by the TinyUSB/NCM tether on the native USB. No no-solder JTAG path exists, and any external JTAG would break the safety wiring under test. (Side note: JTAG being consumed by the safety loops is a mild field-integrity property.)
2026-08-02OD-1 SETTLED — two coverage tools accepted (user-confirmed): pstop_c keeps its own Bullseye coverage + CI (pre-qualified, not re-measured); every other in-scope codebase uses GCC-14 gcov/gcovr (line + branch + -fcondition-coverage MC/DC).Rationale: pstop_c is pre-qualified with its existing Bullseye evidence, so re-tooling it buys nothing; new code standardizes on the free GCC-14 flow already wired into scripts/coverage.sh + CI. The two tools never measure the same code, so there is no cross-tool comparability problem.
2026-08-02MC/DC toolchain = GCC 14 (-fcondition-coverage)Installed 14.3.0 via ubuntu-toolchain-r/test PPA; gcov-14 + gcovr condition coverage confirmed. No Bullseye license needed.
2026-08-01Safety standard = IEC 61508 (SIL) + ISO 13849 (PL)
2026-08-01pstop_c = pre-qualified (referenced, not re-verified)Add only interface/integration requirements at its boundary.
2026-08-01Integrity target = SIL 3 / PL e; coverage bar = statement + branch + MC/DCHARA confirms PL e; SIL 3 allocated (see gates in §3).
2026-08-01Firmware structural coverage = on-target ESP32 gcovHardest path; transport TBD (JTAG vs UART vs console dump).
2026-07-23Option C dropped (LFSR/PRNG + stamp signature)A's both-phase covers stuck-at; deterministic challenge reproducible by a same-firmware fault.

2. Open decisions (need input)​

IDDecisionStatus
OD-1Bullseye vs GCC-14 gcovSETTLED 2026-08-02 (user-confirmed) — see Decisions log §1: pstop_c keeps Bullseye; everything else GCC-14 gcov.
OD-2Vehicle-level HARA inputs (assumptions A-01..A-10 in hazard_analysis_and_risk_assessment.md) — mass, speed, exposure, demand rate W, process safety timeIntegrator must supply. SIL sits at 2 (W2) vs 3 (W3) pending W.

3. Open safety items (from HARA / FMEA)​

3a. SIL 3 quantitative-claim gates (from HARA)​

  • G-FMEDA — no FMEDA yet: SFF / DC / PFH / β unquantified. Blocks any numeric SIL 3 claim.
  • G-CCF — common-cause coverage rests on Option B diversity; not quantified. Single toolchain / identical cores → limited HW diversity.
  • G-SC3 — systematic-capability (SC 3) process evidence beyond MISRA not yet demonstrated (static analysis in CI, unit + fault-injection tests, traceability — this effort).

3b. Dangerous-Undetected register (from FMEA — prioritized)​

  • DU-4 (F-H-02) / SR-H-03 — DONE 2026-08-02: host runner now validates timing config at startup (cfg_validate, floors mirror the ROS2 node: heartbeat [50,1000], max_missed [1,5], min_stop ≥100) and refuses to start on unsafe values. Test tools/test_config_floor.py (7/7). Host coverage 70.4→71.4% L / 56.8→57.9% B / 58.3% MC-DC.
  • DU-1 (F-R-01) — DONE 2026-08-02 (SR-R-09, 1cfbd9a): remote now periodically re-verifies the E-stop GPIO pad config (gpio_ll_get_io_config every ~10 ticks) and forces a controlled clean reset on mismatch (gpio_cfg_fault). Closes the "GPIO config-integrity check" the design doc claimed but code never implemented. Remaining: on-bench fault-injection of a live pad-config corruption (bench-blocked, low value — logic is host-reasoned).
  • DU-2 (F-H-03 / F-P-03) — DONE 2026-08-02 (SR-H-04): remote-side dual-core mutual clock cross-check + ESP32 TWDT/IWDT/RTC-WDT (347de6f, fault-injection validated). NOTE: this covers the remote clock; the machine-side get_time_cb freeze (host runner / ROS2) still wants an analogous guard — track as SR-H-04b.
  • DU-3 (F-R-02) — DONE 2026-08-02 (SR-R-03, d96129c): verified Option-B diverse expressions produce distinct object code (survive the optimizer, not collapsed to one form) + scripts/check_estop_diversity.sh CI guard so a future refactor can't silently erase the diversity. See docs/safety/du_3_object_code_diversity.md.
  • DU-5/DU-7 (F-R-04/05): lockstep blind to common-mode faults in shared downstream code (encode / memcmp / priming, β≈1).
  • Remaining 3 DU rows — see fmea.md §3.

3c. HARA top risks​

  • H-03 — DERP failover dark window (5–9 s) likely exceeds process safety time for a short-PST vehicle. Needs PST budget (OD-2) + a mitigation (faster failover / local-STOP latch).
  • H-10/H-12 — software machine has no rated final element: host/ROS2 STOP is explicitly non-safety. The ESP32 machine (dual series relays + feedback, HFT=1) is the only rated actuator. State clearly in the safety case; software-machine deployments are non-safety-rated.
  • machn double relay-weld — proof-test-interval item for PL e (from FMEA).

4. Doc reconciliation (DONE 2026-08-02 — task #11)​

Applied. Register: docs/safety/documentation_and_code_reconciliation.md (R-01…R-09). The docs were annotated in place — stale text retained with bold status markers + inline [Reconciled 2026-08-02] notes; wrong values corrected directly.

  • §5.5/§6/§10#8 claim a runtime GPIO config-integrity check — never implemented (root of DU-1). Marked NOT IMPLEMENTED (R-02).
  • §5–§7/§9/§10 describe Option C (LFSR challenge, S^=(Cn^En) accumulator, stamp signature) as live — dropped; real controls Option A (both-phase) + B (diverse expressions). §5 marked DROPPED 2026-07 (R-01).
  • Heartbeat-timeout formula/values stale: hb×(max_missed+1), 1000×2 → corrected to hb×max_missed, 400×3 ≈ 1.2 s in integrity_chain_safety_design.md §5.6, MULTI_REMOTE_VALIDATION and the machine_app_runner.c:721 comment (R-03/R-07/R-08).
  • §5.4 "semantic 1oo2D" vs byte memcmp — framing reconciled: byte-identical healthy encodings make memcmp sufficient (semantic comparator made unnecessary, not skipped); common-cause still needs FMEDA (R-04).
  • Whole doc SIL2-framed → SIL3/PLe banner added; rationale retained (R-05).
  • NEW (R-09): §2 "rolling drive level" description was itself stale (one-phase-per-tick) — code samples both phases every tick and ignores the counter (main.c:269); marked superseded.

5. Coverage status & next steps​

Full report: docs/safety/coverage_report.md.

CodebaseToolBaselineStatus
Host machine (machine_app_runner.c)gcov/gcovr (→ gcc-14 MC/DC)70.4% line / 56.8% branch (fn 90.5%)Baseline done; re-run under gcc-14 for condition coverage; drive branch up.
ROS2 machinecolcon + gcov/gcovrjson_lite 84% line / 53% branch; node+backends 0%Baseline done. Only 2/114 TUs execute — node/backends have no unit tests (primary debt).
Remote firmware (decision core estop_verdict.c)host harness gcc-14100% line/branch/MC-DC (39/39)Hybrid (on-target JTAG infeasible). Extracted from main.c behavior-preserving; firmware builds clean. On-bench smoke of this build DEFERRED — bench DERP-relayed, DUT HTTP unreachable.
pstop_cBullseye (own CI)referencedPre-qualified; not re-measured here.

Firmware extraction — real-HW validation (2026-08-02): OTA'd build 2a4d77c to a local unit pstop-01d7ed38 (10.43.0.195, on usb0, no loop relay). Confirmed on-target: clean boot, loops settle to 1/1/1/1, verdict OK, mm=0 (no nuisance divergence post-extraction), bonds to a local machine streaming OK/crc=ok/black-channel echo. DONE 2026-08-02: press→STOP / discordance / arm all validated on the (now-reachable) remote DUT 100.75.70.74 over the fixed WG safety link — arm held 3023 ms, press→STOP, single-channel discordance → STOP via liveness with 0 STOP-msgs on wire. Deferred smoke fully closed.

DUT reachability diagnosis (2026-08-02). From this host, PSTOP06 (pstop-01d7f344 @ 100.75.70.74) is reachable only via DERP(sfo) relay — tailscale ping succeeds (~40-120 ms) but no direct path forms (8/8 relayed, "direct connection not established"). Over DERP, disco pings + the TCP 3-way handshake complete, but HTTP data never returns ("Empty reply"/timeout), independent of HTTP version, an MSS clamp (1140), or a relay-3 power-cycle. Ruled out: httpd wedge (power-cycle didn't fix), MTU size (path MTU ~1250; clamp didn't fix), HTTP/1.1 keep-alive (HTTP/1.0 also fails). My NAT is friendly (MappingVariesByDestIP: false), and the DUT fw already has the direct-path-healing + send-fail fixes (66df9ae) — so the blocker is the remote bench's NAT/firewall preventing direct WireGuard hole-punching, forcing a DERP path that carries tiny packets but not the DUT's HTTP responses. (Earlier this session the direct path did form intermittently — 16 ms — so it's flaky, not permanently blocked.) The DUT's outbound path is fine (fleet check-ins work). Not fixable by reflashing. Remediation: run the HIL suite on the bench host (local USB-NCM, direct) as designed; or fix bench-side direct connectivity (subnet router / open WG UDP / DERP alignment). Fleet-push can still update DUT firmware (DUT pulls outbound) but won't give interactive HTTP for the smoke.

  • docs/safety/coverage_report.md (host + ROS2 + firmware-core baselines). CI gate pending (task #10).
  • Requirements-driven coverage (task #9) DONE — requirements_traceability.md: 25/39 SRs have a test (64.1%), 13/39 strictly verified (33.3%); 22/27 functions traced (81.5%). 13 unverified-gap SRs = the task #10 roadmap. SR-R firmware is weakest (8/15 unverified).
  • Function→SR holes to resolve: F-R-10 admin/web needs a non-interference SR; F-H-04, F-M-01, F-M-06 need an SR or explicit non-safety scoping.

6. Deferred from prior safety work (A/B implementation)​

  • Design + FMEA doc — now being produced by this effort (supersedes the earlier deferred item).
  • Upstream pstop_c far-Hamming status-encoding memo (for maintainers).
  • Automated diversity HIL regression (fault-injection build → assert divergence).
  • GitHub push DONE 2026-08-02 (per user): origin/pstop = e40191d — A+B safety work, full docs/safety/ chain, coverage + gap closures. Secrets-audited clean (no fleet IP/creds; internal test IPs consistent with existing repo norm). [ ] pstop→main PR still open for reviewer sign-off.

6b. Safety requirements (DONE 2026-08-02 — task #5)​

docs/safety/safety_requirements_specification.md — 39 requirements (SR-<area>-nn): SYS 8, R 15, H 6, M 6, I 4. Status: 18 Satisfied · 8 Partial · 11 Gap · 2 Residual-accepted. Every DU-1..9 discharged; SG-6 fully discharged, SG-1..5 partial. SIL3/PLe kept allocated, not achieved. Gap requirements = the implementation roadmap:

  • SR-H-03 host config-floor validation (DU-4) — cheapest/highest value.
  • SR-R-09 GPIO config re-verify → STOP (DU-1).
  • SR-H-04 frozen-clock diagnostic (DU-2).
  • SR-R-03 Option-B object-code-diff gate (DU-3).
  • SR-R-08/12 comparator/encode common-mode (DU-5/7); SR-R-10/11 pull-down CCF + settle (DU-6/8); SR-R-15 NVS peer-slot integrity.
  • Full list + trace in safety_requirements_specification.md §7–§9.

7. Next steps (working queue)​

  1. Deep doc reconciliation (task #11) — DONE. Derive requirements (task #5) — DONE.
  2. Structural coverage baselines: re-run host under gcc-14 (line+branch+MC/DC); ROS2 (task #7); firmware on-target (task #8).
  3. Requirements-traceability + coverage (task #9): map each SR → code → test; report % SRs with a verifying test + % safety lines traced to an SR.
  4. Implement Gap requirements (SR-H-03, SR-R-09 first) as SR-traced tests land — drives both coverages up.
  5. Aggregate report + CI gate (task #10) — DONE 2026-08-02: scripts/coverage.sh + .github/workflows/coverage.yml (ros:humble + gcc-14), CI green (2m27s). Aggregate: firmware core 100% / host 71.4% / ROS2 52.4% line.
  6. firmware HIL press/discordance/arm DONE; SR-H-04 frozen-clock DONE (SR-H-04b); DU-3 object-code check DONE.

8. What actually remains (rev 2 — 2026-08-02)​

Needs your input (blocking, I cannot proceed without it):

  • FMEDA firm-up (OD-2): real ESP32-S3 + relay part numbers, demand rate W, and proof-test interval — moves SIL 3 from allocated/assumed to demonstrated, and sets W2(SIL2) vs W3(SIL3). Also unblocks HARA H-03 (DERP failover 5–9 s dark window vs process-safety-time budget; may then need a local-STOP latch mitigation). HOW-TO written 2026-08-02: docs/safety/fmeda_firm_up_guide.md — the full playbook for firming up the FMEDA without manufacturer λ data (data sources SN 29500/exida SERH/FMD-2016/FIDES, π-factor conversion, DC-from-IEC-61508 tables, B10d relay route, Annex D β, the assumptions register, sensitivity analysis, proven-in-use). Top pstop actions: protect the Type-B SFF-90 % boundary, introduce relay diversity (≈halves PFH since PFH≈β·λ_DU), and stand up a fleet field-hours + classified-fault log now for a future proven-in-use / Route-2H argument.
  • OD-1 SETTLED 2026-08-02 (user-confirmed): two-tool split accepted — Bullseye stays with pstop_c, GCC-14 gcov everywhere else. Documented in coverage_report.md policy note + Decisions log §1.

Blocked on hardware / people:

  • machn (ESP32 machine) clock-guard HW validation — DONE 2026-08-02 (docs/safety/machine_clock_guard_hil_validation.md). Live node machn-01d77a1c was running the pre-guard 6e7492f; built HEAD, OTA'd the guard build 3dcd08d, watched ~5.5 min: clean boot, no false clock-fault trip, cores in lockstep, and a full soft-remote arm→RUN (0.53 s)→STOP→re-arm→safe-STOP with the real series relays. Genuine esp_timer-freeze detection remains fault-injection-proven on the remote analog (347de6f) + host tests (freeze can't be injected on unmodified silicon).
  • pstop→main PR reviewer sign-off (your call; not reminding).

Open engineering, lower priority:

  • ROS2 node/backend coverage — DONE 2026-08-02 (16649d1): spun-executor + loopback-HTTP-stub suites raised the hand-written ROS2 package ~53 % → ~89 % line (machine_bridge_node 43→98 %, hardware_backend 25→99 %), tests 27→56 all green; SR-M-01 + SR-M-03 promoted to Verified (requirements coverage 25→27/39 with a test, 13→15/39 strict). Residuals are honest (live-UDP-remote paths + OOM guards). Host branch-path headroom remains.
  • Remaining DU rows: DU-5/7 comparator/encode common-mode (β≈1 shared downstream), DU-6/8 pull-down CCF + settle, SR-R-15 NVS peer-slot integrity.
  • On-bench fault-injection of SR-R-09 pad-config corruption (bench-blocked, low value).
  • Markdown normalization stays deferred (repo-wide). Pre-commit code hooks (polymath-cpp clang-format + cpplint) were applied to this session's new C++ (16649d1). The markdown hook rewrites -→+ list markers, but all ~30 committed docs use - and none use +, so applying it per-file creates inconsistency — it's a one-shot repo-wide reformat for a maintainer.
  • Upstream pstop_c far-Hamming status-encoding memo (maintainers).