# CLAUDE.md — Rules for Claude on the Argonaut 3 Project **Version:** 3.6 **Updated:** 11 July 2026 **Changelog 3.6:** Added measurement-before-diagnosis, step-back-on-break, one-restart-then-verify-9001, and Cockpit widget re-import rules to Working Style. Corrected the Documentation discipline note that incorrectly claimed CLAUDE.md is never committed to Gitea — it is tracked and committed like any other repo file. **Changelog 3.5:** Added `[VSC]` terminal label. Added SESSION LOG section + write triggers (bridges Claude Code work back to claude.ai chat, which has no native access to Claude Code sessions). --- ## PRIME DIRECTIVES — These override everything else ### 1. Research before acting. Always. Before any technical decision, code change, or command: - Read project docs (handover, UI design, DIR, this file) and search past chat history - Read source code and official docs before acting; GitHub source/issues are more authoritative than official docs for Cockpit/BlueOS specifics - Confirm facts from sources, not from memory If uncertain: **search first, then act**. Never guess. ### 2. Time and tokens are finite and valuable - **Time wasting is FORBIDDEN. Token wasting is FORBIDDEN.** - Short replies. No preamble padding. To the point. - Never repeat an approach that has already failed - Never ask for information already in the project docs - Never issue a command without knowing what it will do and why ### 3. Facts only. Never assumptions. - Do not proceed based on what "should" work - Do not assume port, routing, or API behaviour — measure it - Do not infer system state — verify it before issuing commands - If a claim cannot be verified from a source, say so explicitly - Never present a prior session's confirmed state as the current running state --- ## Working Style ### One command at a time Give one command, wait for feedback, then give the next. Never batch commands unless asked. ### Measurement before diagnosis (facts only) Every claim about system state must be backed by a read-only command output in the same exchange. No diagnosis from theory. If a cause is asserted, the measurement that proves it must be shown first. If state is unknown, the only permitted next action is the command that measures it — not a fix built on assumption. Enforcement phrase: if the operator says "measurement?" and none was shown, the rule was broken — go measure before proceeding. ### Step back to the last action when something breaks When something that was working breaks, return to the last action taken. If it worked before that action and not after, that action caused it — revert or step back from there before investigating anything else. Do not open new diagnostic threads before ruling out the most recent change. ### One restart, then verify — never restart-storm Never issue repeated `systemctl restart argonaut.service` in quick succession. Rapid cycling leaves cockpit_bridge wedged (9001 never binds; node runs but serves nothing). Always: one restart, wait ~15s, then verify with `ss -tlnp | grep 9001` before any further action. Only one restart is in flight at a time. ### Cockpit widget re-import — full quit after JS changes When a Cockpit DIY widget's JS changes (not just html/css), a delete + re-import inside the running app can leave the old script's setInterval timer alive in the background, so old and new timers both write to the DOM and fields flicker. After re-importing a widget whose JS changed, fully quit and relaunch Cockpit (not just close the window — the Electron process survives a close). html/css-only changes do not need this. ### Lead with context and corrections BEFORE instructions, never after. ### Copy-paste friendly commands Commands inside terminal blocks must be immediately copy-pasteable and suitable for the terminal in use (Git Bash on the laptop, bash on the Pis/server). Never include: - Placeholders like `` or `path/to/file` - Hashed `#` comments inside the command block - Options that require editing before running ### Code comments When writing code (Python, config, scripts), always include full inline comments explaining what each section does and why. This is a standing user preference. NOTE: this applies to CODE files only — NOT to terminal command blocks, which must stay clean and copy-paste ready with no inline comments. ### Terminal labels Every command block is preceded by a plain-text terminal label above the block. Grant pastes terminal output including the shell prompt — use these to identify which device the output came from: | Label | Shell prompt | Device | IP | |---|---|---|---| | `[SERVER]` | `grant@symbytech:~$` | Dell PowerEdge R210 | 192.168.1.175 | | `[RPi5]` | `ubuntu@rov-brain:~$` | Raspberry Pi 5 autonomy brain | 192.168.1.101 | | `[RPi4]` | `pi@blueos:~$` | Raspberry Pi 4 BlueOS gateway | 192.168.1.100 | | `[LAPTOP — Git Bash]` | `grant@BB8 MINGW64 ...` | Windows field laptop BB8 | — | | `[VSC]` | — | VS Code integrated terminal on BB8 (local Windows session at `C:\Users\grant\Desktop\Autonomous ROV System`) | — | | `[BROWSER]` | — | Web browser on laptop | — | | `[COCKPIT]` | — | Cockpit native desktop app on laptop | — | `[VSC]` is a **local Windows session on BB8, NOT a Remote-SSH session to rov-brain**, unless explicitly stated otherwise. Do not assume `[VSC]` == RPi5 working copy. Never put the label inside the bash block. > The BlueOS KVM VM (`blueos-vm`) was decommissioned once RPi4/RPi5 hardware became > operational. `[VM]` is no longer a valid label. Any VM artefacts remaining on the > server are for reference only. ### Verify before modifying Always read a file before modifying it. Always check current state before changing it. State what you observed before proposing a change. ### File operations Read the exact file first, then `str_replace` with exact matching text. Remove old versions before writing new ones. Complete files, not fragments — no partial code. ### No guessing at code When writing code that interfaces with an external system: 1. Find the official documentation or source code 2. Find a working example from the official repo or community 3. Build on the confirmed example Never invent API calls, message formats, or connection strings. ### Own mistakes once Acknowledge an error once and fix it. No repeated apologies, no grovelling. ### KISS Recommend the simple correct path, not a menu of options. --- ## CREATE-THEN-BACKUP (non-negotiable) When any new component (device, drive, service, config, OS install) is created or configured, a backup must be put in place immediately as part of that work — not deferred. Backups and foundational architecture (storage, recovery, monitoring) are prerequisites, prioritised BEFORE new feature/build work. The RPi4 SD failed with no backup because this was deferred — do not repeat. --- ## Project-Specific Rules ### Cockpit DIY widgets - Cockpit is **v1.18.2** (native desktop app mandatory — browser/Lite extension does NOT support DIY widgets) - JSON format with `html`/`css`/`js` string fields - CSS hard constraints: no `body {}`/`:root {}` global blocks, no `@keyframes`, no unicode in JS strings, no `position: fixed`, no global tag selectors - No `async/await` in widget JS — use `.then()/.catch()` - Use `var` not `let`/`const` in widget JS for compatibility - Correct live-value getter: `window.cockpit.getDataLakeVariableData(variableId)` (CONFIRMED v1.18.2). `getDataLakeValue()` and `listenToDatalakeVariable()` do NOT exist in DIY widget scope. `getAllDataLakeVariablesInfo()` is metadata-only. - Widget JSON files in the repo use underscore naming: `w6_altitude.json` (matches W0–W5). The Cockpit-loaded widget is stored by internal config, not filename, so the repo filename is the canonical reference. ### Data injection into Cockpit data lake - ONLY working external-injection method is the Generic WebSocket Connections feature (Settings → General). Production path is the `cockpit_bridge` ROS2 node (rov_mission, WebSocket :9001). - Message format: `variable-name=value` (one per send). Data lake key: `external/{name}`. - ALWAYS use hyphens — Cockpit normalises underscores to hyphens in IDs. - HTTP POST to mavlink2rest does NOT work for data lake injection. MAVLink UDP injection from external scripts does NOT work. Tested and confirmed — do not suggest them. - Current data lake variables published by `cockpit_bridge` (19 total — 8 telemetry + 11 mode-profile flags): | Variable | Source topic | Meaning | |---|---|---| | `external/rov-failsafe` | `/rov/failsafe` | 0=GREEN, 1=AMBER, 2=RED | | `external/rov-depth` | `/rov/depth` | Depth in metres | | `external/rov-voltage` | `/mavros/battery` | Battery voltage | | `external/rov-heading` | `/mavros/mavros/compass_hdg` | Compass heading degrees | | `external/rov-ms` | `/rov/mission/status` | Mission state integer | | `external/rov-mp` | `/rov/mission/status` | Mission progress 0-100% | | `external/rov-recording` | `/rov/recording/active` | 1=recording, 0=not recording | | `external/rov-altitude` | `/ping2/altitude` | Ping2 altitude metres (-1.0=NO SONAR) | | `external/rov-mode` | `/rov/mode/profile` | Active profile name: ROV / AUV / hybrid | | `external/rov-mode-base` | `/rov/mode/profile` | Base mode: 0=ROV, 1=AUV | | `external/rov-hilt` | `/rov/mode/profile` | HILT: 1=present, 0=absent | | `external/rov-health-role` | `/rov/mode/profile` | W1: 0=advisory, 1=gate | | `external/rov-rec-control` | `/rov/mode/profile` | Recording: 0=manual, 1=auto | | `external/rov-nogo-gate` | `/rov/mode/profile` | No-go: 0=relaxed, 1=enforced | | `external/rov-rec-reminder` | `/rov/mode/profile` | ARM reminder: 1=on, 0=off | | `external/rov-return-mode` | `/rov/mode/profile` | Return: 0=operator-selectable, 1=autonomous | | `external/rov-live-telem` | `/rov/mode/profile` | Telemetry: 0=on, 1=predive-only | | `external/rov-nav-display` | `/rov/mode/profile` | Nav: 1=shown, 0=autonomy-only | | `external/rov-camera-role` | `/rov/mode/profile` | Camera: 0=pilot-primary, 1=record-only | ### FastAPI backend (rov_api) - `rov_api` package provides the HTTP-to-ROS2 bridge on port 8081. Runs as `argonaut-api.service` (manual-start in dev, same policy as argonaut.service). - Architecture: uvicorn/FastAPI in the main thread, an embedded rclpy node (`RovApiNode`) spinning in a daemon background thread. HTTP handlers call thread-safe methods on the node. Do NOT call `rclpy.spin()` more than once. - Core endpoints (Stage 2, live): `GET /health`, `POST /abort` (W3 RETURN TO SAFE, publishes Bool to `/rov/mission/abort`), `POST /mission/start` (MissionCommand START), `POST /mission/stop` (MissionCommand ABORT). - Deferred to Stage 2b: `/return_budget` (W5), `/backup/status` + `/backup/delay` (W7, Option A design), `/mission/upload`. - **W3 abort path is vehicle-layer:** publishes Bool(true) to `/rov/mission/abort`, which failsafe_monitor latches as `flag_manual_abort` → FSM NORMAL → HOLD_AND_RECOVER. This is DIR-5 return-to-safe, NOT the mission ABORT service. Do not confuse the two. - **Python dependencies:** fastapi + uvicorn installed system-wide on RPi5 via `sudo pip3 install fastapi uvicorn --break-system-packages --ignore-installed typing_extensions`. The `--ignore-installed typing_extensions` flag is required — the Debian-managed typing_extensions has no pip RECORD file and blocks the install otherwise. ### Deploy files (version-controlled) - systemd units and launch wrappers live outside the workspace but are version-controlled in the repo `deploy/` folder (copies; authoritative running copies at deploy locations). - `deploy/argonaut.service` → `/etc/systemd/system/argonaut.service` - `deploy/argonaut-stack.sh` → `/usr/local/bin/argonaut-stack.sh` - `deploy/argonaut-api.service` → `/etc/systemd/system/argonaut-api.service` - `deploy/argonaut-api.sh` → `/usr/local/bin/argonaut-api.sh` - `deploy/README.md` documents locations, pip deps, and install procedure. - `/home/ubuntu/ros2_ws` is a symlink to `/data/ros2_ws` — both wrappers rely on it. ### Recording architecture (DIR-9a — ARM-bounded black-box) - Recording is a **single ARM-bounded tier** for black-box/debug/ML/fault-finding via `recording_manager`. **AUV:** starts on ARM, stops on DISARM. **ROV:** operator start/stop; the ARM edge fires a reminder ("armed, not recording"). Keyed to the `recording_control` flag, not the mode. (Supersedes the old continuous DIR-9 model.) - **No-go coupling:** AUV `enforced` — recording fail → mission stops, vehicle recovered. ROV `relaxed` — recording fail after ARM → pilot alerted; operator resolves in-water or recovers. - Certified deliverable capture is OUT of scope pending Class-approved devices; recorded data is dev/debug/ML only, never a client deliverable until then. - The recorder manages a `ros2 bag record` subprocess — MCAP format, zstd compression, 500MB bag splitting, 10GB free-space retention on `/data`. - **`foxglove_bridge` is visualisation only — it does NOT record MCAP.** Correct this wherever the old "MCAP via foxglove_bridge" claim appears. - Bags written to `/data/bags/dive_/` on NVMe. - No camera in the topic set currently — recording ~2.5 MB/s (nav/MAVLink only). - **Storage (no reliable in-field server backup):** primary lever is LOWER PUBLISH RATES on heavy streams (keep topics present for debug); ARM-bounded runs self-limit; retention cap tunable; NVMe expansion later. Topic/rate review is an open action. ### Operating-mode profiles (DIR-1/2, mode_profiles.yaml) - One widget set; behaviour is set by flags from a mode profile. Dominant axis is HILT (tether present = ROV / live interfaces; absent = AUV / headless). - **Source of truth:** `mode_profiles.yaml` in `rov_mission/config` — ROV/AUV defaults plus custom hybrid via "Save As". Hybrid is NOT a third mode; it is any saved profile with overrides. - `mode_profile_loader` (rov_mission) resolves the active profile into a `ModeProfile` message (rov_interfaces), publishes it LATCHED on `/rov/mode/profile`, reloadable via the `/rov/mode/reload` (std_srvs/Trigger) service. Fail-safe: invalid flag values are rejected and the last-good profile is retained. - `cockpit_bridge` surfaces the 11 flags to the data lake as `external/rov-*`. Widgets read the flags, NOT the mode. - Flags: hilt, health_role (advisory|gate), recording_control (manual|auto), nogo_gate (relaxed|enforced), record_reminder_on_arm, return_mode (operator_selectable|autonomous), live_telemetry (on|predive_only), nav_display, camera_role (pilot_primary|record_only). - **YAML gotcha:** YAML 1.1 parses bare `on`/`off`/`yes`/`no` as booleans — quote enum values like `"on"`. The loader also normalises bool→string as a guard. ### BlueOS / MAVLink router - **MAVLinkRouter** is the confirmed router. Do NOT recommend MAVP2P — BlueOS itself warns "MAVP2P has been presenting issues and is not currently recommended." - MAVP2P was tried and reverted. Do not suggest it again. - **ardupilot-manager FD exhaustion — CORRECTED ROOT CAUSE (9 Jul 2026).** The earlier claim that this was "fixed permanently" by `/etc/docker/daemon.json` `nofile: 65536`, with root cause "UI polling TCP sockets," is WRONG on both counts. The `nofile` change only raised the ceiling (failure at ~10 min → ~11 h of cumulative exposure); it fixed nothing. The leaked FDs are `/dev/i2c-1` handles, NOT TCP sockets. Measured mechanism: BlueOS's Autopilot Firmware / board-config page polls `GET /v1.0/available_boards`; each request triggers 20 `flight_controller_detector.linux.detector:detect_boards` probes for Navigator / NavigatorPi4 / NavigatorPi5 / Argonot. Each Linux-board probe opens `/dev/i2c-1` and never closes it on failure. We run a Pixhawk 2.4.8 on USB serial (PlatformType.Serial), so every probe fails and every probe leaks. Measured leak rate ~100–108 FD/min with the board-config page open; exactly zero with it closed (4630 flat over 65 min, then +90 FD in 50 s on opening the page, flat again immediately on closing). Confirmed cause-and-effect, reversible. Note: FD count accumulates across sessions and only resets when `blueos-core` is recreated (core restart/reboot) — cumulative page-open time, not wall-clock, drives time-to-failure. At 65536 FDs, ardupilot-manager can no longer open new handles → AUTOPILOT_VEHICLE_TYPE_FETCH_FAIL, AUTOPILOT_ENDPOINT_FETCH_FAIL, BAG_OF_HOLDING_GET_DATA_FAIL, "No board running". Core restart recreates the container with a fresh FD table, which is why restarting always "fixed" it. This is an upstream BlueOS 1.4.3 bug; do NOT patch BlueOS (updates would revert it). - **Mitigation (dev):** do not leave the BlueOS Autopilot Firmware / board-config page open. Open, act, close. Other BlueOS pages and Cockpit are unaffected. - **Mitigation (field) — NOT YET IMPLEMENTED:** operator discipline is not a field-safe control. The exposure is launching with a nearly-exhausted FD table after pre-dive setup, not leakage during the dive itself. Required: (a) FD-count check on ardupilot_manager as part of the AUV pre-dive gate, blocking launch above a threshold; (b) failsafe_monitor must detect FC comms loss so W1 goes RED when the board is unreachable. See open items. ### Hardware facts (do not re-question) - **RPi5 storage:** boots from SD card (`/dev/mmcblk0`). NVMe holds DATA ONLY (`/data` — ros2_ws, bags, logs). Backup images the SD for OS, rsyncs `/data` for data. - **Flight controller:** Pixhawk 2.4.8 on `/dev/ttyACM0` is the active flight controller. SITL is NOT used in production. BlueOS `preferred_board` = Pixhawk1. - **Tailscale on RPi5:** installed but stopped and disabled (`tailscaled.service` disabled 2 Jul 2026). Re-enable with `sudo systemctl enable --now tailscaled` if remote access to RPi5 is needed. ### Hostname resolution (DNS) - Hostnames `symbytech` (192.168.1.175), `blueos` (192.168.1.100), and `rov-brain` (192.168.1.101) are resolvable from the laptop (Windows hosts file) and RPi5 (`/etc/hosts`). Grant prefers hostnames over IPs. - Git remotes use the hostname form: `ssh://git@symbytech:222/administrator/rov-autonomy.git` - If a "Could not resolve hostname symbytech" error appears on a machine, that machine is missing the hosts entries — add them rather than switching remotes to IPs. ### Backup system (built 30 Jun 2026) - Push model: each Pi self-backs-up via root cron, pushes to server over SSH (no inbound access to Pis). - RPi4: `/usr/local/bin/rov-brain-pi4-backup.sh`, 00:00 daily. - RPi5: `/usr/local/bin/rov-brain-pi5-backup.sh`, 00:30 daily (staggered). - Server monitor `check_rov_backup` alerts on FAILED (always) and OVERDUE (≥2 days, online or offline, throttled weekly). - Field/ops backup model is operator-aware, never unattended mid-dive (DIR-11, W7). ### argonaut.service / argonaut-api.service (RPi5) - Manual start is deliberate for both. Verify BlueOS heartbeat first, then `sudo systemctl start argonaut.service`. Do NOT enable auto-start until field-ready. Restate this rule each time it comes up. - `argonaut-api.service` orders After=argonaut.service but does not Require it — the API can start independently for /health checks; mission commands return a clear "service unavailable" error if the stack is down. ### ROS2 code - Check `rov_interfaces` message definitions before writing any node code - Use `self.get_logger()` not `print()` - Do not hardcode IPs, ports, or thresholds - Do not deploy `rov_simulation` in production launch files - Editing an installed launch/config requires `colcon build --packages-select ` — the running launch uses the installed copy under install/, not src/ - ARM64 Docker builds on the laptop only (buildx), never on the server ### Git workflow - Commit to `master` on `administrator/rov-autonomy` - Gitea SSH remote (hostname form): `ssh://git@symbytech:222/administrator/rov-autonomy.git` - Both laptop and RPi5 resolve `symbytech` via hosts entries (see Hostname resolution). - Widget JSON files are re-imported from the laptop (source of truth for widgets) and pushed to Gitea — never overwritten from chat history. - UFW rules before starting new services on new ports. ### Security — deferred to pre-field hardening pass - **UFW:** currently inactive on RPi5. Deferred because there is no online exposure (Tailscale disabled, no proxy rules to the Pis). When enabling: allow SSH (22), operator TCP (8765, 9001, 8081), and all traffic from LAN subnet 192.168.1.0/24 (covers ROS2 DDS dynamic ports — do NOT try to pin them individually), then default deny incoming. Bundle with SSH password change. Trigger: before remote ops or field. - **RPi5 SSH password:** weak dev password, password auth enabled. Change to strong password before field deployment. --- ## What Claude MUST do - State confirmed facts before proposing an action; cite the source of any technical claim - Acknowledge when something has already been tried and failed - Provide complete, working code (fully commented) based on confirmed examples - Verify system state before and after changes - Update the handover doc at the end of each session (version-controlled, changelog) ## What Claude MUST NOT do - Guess at how a system works and propose commands based on that guess - Repeat a failed approach - Issue commands whose outcome is uncertain - Provide partial code requiring manual editing to work - Assume a system is stable without checking - Use `print()` in ROS2 nodes; hardcode addresses/ports/thresholds - Build x86 Docker images for vehicle deployment - Change the failsafe priority order without design review - Present prior-session state as current running state - Recommend MAVP2P as the MAVLink router - Commit docs to Gitea — only code and README go to Gitea; docs live in Claude project knowledge --- ## Documentation discipline - Other project docs (handover, UI design, DIR, sim/ML plan) are maintained as COMPLETE replacement files uploaded to Claude project knowledge — never patches or fragments, never committed to Gitea. - CLAUDE.md is different: it IS version-controlled and committed to Gitea like any other repo file (normal patch/diff edits and commits apply to it, not the replacement-file workflow above). - Version numbers and changelogs maintained on every update. - Only code, README files, and CLAUDE.md go to Gitea. --- ## SESSION LOG (bridges Claude Code → claude.ai chat) **Why this exists:** Claude Code (VS Code) and the claude.ai chat are separate systems with no shared memory. Claude Code reads this file at session start but the claude.ai chat cannot see Claude Code's session history at all. This log is the bridge. **Format:** `YYYY-MM-DD — one-line summary of decision/change (commit hash if applicable)` **Claude Code MUST append an entry here at the end of any session where one of these happened** — not for routine edits, refactors, or in-progress work: - A design/architecture decision was made or changed (DIR-level, mode-flag behaviour, failsafe priority, recording behaviour) - A hardware fact was confirmed or changed (wiring, storage layout, board config) - A security-relevant change was made (SSH, UFW, credentials, exposure) - A backup/recovery-relevant change was made - A bug was root-caused and fixed (not just patched) - A previously-deferred item was completed or its trigger condition changed - Anything Grant would reasonably expect the claude.ai chat to already know next time he opens it **Skip:** typo fixes, comment-only changes, WIP/incomplete work, anything already fully captured by the git commit message with no extra context needed. **Workflow:** Grant pastes new entries into the claude.ai chat when relevant; Claude folds them into chat memory via the memory tool. This log is append-only — do not edit past entries; correct forward with a new dated entry if something needs revising. - 2026-07-05 — Widget housekeeping: added W6 altitude widget, removed probe diagnostics widget (`785ec56`) - 2026-07-08 — rov_api: added GET /mode and POST /mode endpoints (validate-before-write + /rov/mode/reload trigger); deployed and tested on RPi5, all paths verified (9109439) - 2026-07-08 — Root-caused chronic widget "no data": cockpit_bridge (feeds Cockpit data lake on :9001) launched but had no respawn, so a boot-time race left it permanently dead. Added respawn=True to its launch Node. W8 mode indicator built + validated live (render+data). W1/W8 confirmed GREEN/AUV once bridge up. - 2026-07-08 — FLAGGED (open): recording_manager.py still implements superseded DIR-9 (continuous-from-boot recording), NOT the governing DIR-9a (ARM-bounded black-box: AUV ARM-start/DISARM-stop, ROV operator manual + arm reminder). Design decision exists only in handover + CLAUDE.md; code never updated. foxglove_mcap.launch.py comments also still say DIR-9. Also: argonaut-stack.sh passes record:=${RECORD} but rov_full.launch.py declares no such arg, so RECORD=false is silently ignored and bags record regardless. Both deferred to a dedicated DIR-9a recorder-rework session — do NOT wire RECORD into the current DIR-9 model. - 2026-07-08 — Added respawn=True/respawn_delay=5.0 to failsafe_monitor, motion_controller, ekf_filter_node, depth_node, mission_executor. Deliberately NOT applied to: mavros_node (respawn could mask/worsen the chronic FCU-link instability — belongs in that investigation), recording_manager (pending DIR-9a rework), camera_node/feature_detector (perception not operational yet), foxglove_bridge (viz-only). NOTE: failsafe_monitor respawn has a coverage gap during the 5s respawn delay — no failsafe assessment in that window; flagged for future design consideration. - 2026-07-09 — Root-caused the recurring "No board running" / AUTOPILOT_*_FETCH_FAIL failure. NOT the previously-documented TCP-socket/UI-polling cause. Real cause: /dev/i2c-1 FD leak in ardupilot_manager's Linux board detector, driven by the BlueOS board-config page polling available_boards (20 probes/request, ~100-108 FD/min). Zero leak with page closed; confirmed reversible by controlled test (4630 flat 65min, +90 FD in 50s on open, flat on close). nofile=65536 only delayed failure to ~11h cumulative page-open time. Upstream BlueOS 1.4.3 bug — not patching. Corrected the stale root cause in this file. - 2026-07-09 — FLAGGED (open, safety-critical): failsafe_monitor reports GREEN with the flight controller unreachable. W1 is the AUV pre-dive GO/NO-GO gate (health_role=gate) — it would pass a dive with a dead FC. failsafe_monitor must consume /mavros/state connection + heartbeat and drive W1 RED on FC comms loss. W1 status line should carry cause text (e.g. "Vehicle Disconnected", "Heartbeat Lost", "No Board Running"). Also flagged: no FD-count check on ardupilot_manager in the pre-dive gate. - 2026-07-11 — RESOLVED the 2026-07-09 flagged safety-critical fault: failsafe_monitor now drives W1 RED on FC comms loss. Four commits: (1) 86b2728 — comms loss forces unconditional RED (was scored as one marginal parameter, only reached AMBER) plus fcu_ever_connected latch so the startup window before any heartbeat is not reported GREEN; cause text "Heartbeat Lost"/"Vehicle Disconnected" added to FailsafeStatus.message per DIR-7. (2) 1b0050d — FSM recovery fix: RETURN_TO_SAFE was a terminal trap (only HOLD_AND_RECOVER could return to NORMAL); now both recover when conditions clear. (3) dbe9515 — cockpit_bridge publishes new data lake variable rov-failsafe-cause (substring of message before first "|"; empty when nominal). (4) 3409ae5 — W1 shows state word on #w1-message (state-driven, no flicker) and cause text on #w1-footer, footer restyled red 11px, blank when nominal. All four verified live end-to-end: kill mavros -> RED + "Heartbeat Lost", restart -> recover to GREEN. Also corrected: cockpit_bridge was publishing failsafe_state (FSM, can be 3+) not assessment_state (0/1/2) to rov-failsafe — fixed in dbe9515's sibling commit d854641; this was the real cause of the day's "widgets show no data" (value 3 out of W1's range), compounded by a restart-storm wedging 9001. - 2026-07-11 — PARKED for next sessions, in priority order: (a) DOC RESTRUCTURE (do first next session) — consolidate all current-state into the handover as single source of truth, strip state from CLAUDE.md leaving rules only, make SESSION LOG the handover's changelog; DIR and domain docs stay separate. Reason: current-state is smeared across CLAUDE.md, handover and SESSION LOG, causing loss of situational awareness. (b) Mode-dependent failsafe recovery: AUV (health_role=gate) commits to recovery on condition-clear, ROV (advisory) holds AMBER and hands to operator to decide — requires failsafe_monitor to read the mode profile (currently mode-blind). (c) Auto-recovery: failsafe_monitor should attempt to re-establish the FC link itself and only escalate/hold RED if it cannot — real-world has no operator to restore comms. (d) Dedicated "string cause" field in FailsafeStatus.msg to replace the message-substring shortcut in cockpit_bridge. (e) Operator control surface (start/pause/stop buttons via rov_api) and one-command stack startup+healthcheck. (f) DIR and handover updates for the above still owed this session. --- ## Key Reference Documents (current versions) | Document | Purpose | |---|---| | `ROV_Project_Handover_v2_21.md` | Master reference — architecture, environment, commands, status board | | `design_intent_register_DRAFT.md` | DIR-1…DIR-11 (+ DIR-9a) — owner/operator intent, source of truth for *why*; recording now ARM-bounded per DIR-9a | | `ROV_Failsafe_Design_v2_0.md` | Failsafe state machine, sensor roadmap, priority order | | `Argonaut3_UI_Design_v1_9.md` | Cockpit widget specs (W0–W7) and confirmed data paths | | `Mission_and_Data-Recording_Lifecycle_for_AUV_ROV_Systems...md` | Lifecycle research backing DIR-9/10 | | `Argonaut3_Simulation_ML_Tooling_Plan_v1_0.md` | Simulation + ML tooling: dev-PC tiers, cloud options/cost, training assets/datasets/models, PCIe Gen 3 decision | > When referencing these, use the latest version present in the project folder — do not > cite a version number from memory if a newer file exists. --- ## Safety Notice This is a production-oriented autonomous safety system. All code must be treated as if it will run on a real vehicle in open water with no operator present. Safety, correctness, and reliability are non-negotiable. Shortcuts taken during development become risks during field operations.