Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 279 additions & 0 deletions docs/plans/2026-08-15-beast-platform-rebuild.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
# BEAST-01 platform rebuild — tear it down, keep a short list, build it back

**Status:** PROPOSED — 2026-08-15. Owner-directed. This is a **clean-room rebuild**, not a
migration. The new tree starts empty; the default for every file, package and script is *does
not exist*, and anything that survives must be named on the preserve list with a written reason.

It reframes but does not delete the 2026-08-07 strip-down and the 2026-08-14 vendored-surface
plans: those correctly catalogue drift. This one removes the surface that generates it.

## Why a rebuild and not a migration

The first draft of this plan was six migration phases. It was wrong, and the reason is worth
stating because it is the same reason the robot is in this state:

**Subtraction from a bad shape cannot reach a good shape.** Every migration phase has to reason
about the existing system — carry it, guard it, cut over from it — so the old structure
constrains every step. That is exactly how the 1,870 lines of shell were built: thirteen
locally-correct steps, each a reasonable response to the previous one's failure. More
locally-correct steps, even subtractive ones, do not leave a local optimum.

The concrete tell: that draft wrote `provision.sh` by recording the current robot and then
validated it with `--check` **against that same robot**. It would have passed trivially — that
machine has everything, including every undeclared accident — and enshrined them as declarations.

A clean-room rebuild inverts the default. Nothing is carried by inertia. Every file has to be
typed on purpose, and anything we forget shows up as a failure during bring-up rather than as a
silent dependency that works until the day it doesn't.

**Risk profile is not worse than the migration.** The new tree is built offline, alongside, while
the robot keeps running the old stack. Nothing is deleted until the replacement has run on
hardware. The difference is only that the end state is designed rather than eroded.

## The one-sentence diagnosis

**The repository is not the authority on the robot, and no mechanism tells you when it is
wrong.** It is not a code-volume problem — every line count measured this session (2,467 ours;
~8,300 glue; ~28,000 inert vendor) failed to explain the pain, because the pain is structural.

1. **Nothing in the repo declares what runs.** `beast-install-systemd-units` copies all 17 unit
files and never runs `systemctl enable`. Which 9 of 17 start at boot exists **only** as
symlinks in `*.target.wants/` on one NVMe, created by hand across 35 days.
2. **Nothing in the repo declares what the machine is.** 17 facts live only on that disk —
including a one-line udev rule (`03e7`, `MODE 0666`) without which the OAK-D will not open,
five group memberships, ~28 hand-installed `ros-humble-*` packages, four third-party apt
sources, and `pip install --user smbus2`, on which the whole power path depends.
3. **Mechanisms report success when they fail.** Seven observed this week. This is the one that
makes debugging cost 10× — no layer's green light is admissible, so every investigation
restarts from "is any of this even true?"

## Evidence tier — observed vs. read

Stated explicitly because an earlier draft blurred them and inflated the count by one.
(`beast-ctl`'s `mask` verb failed **loudly** — a fictional capability, not a silent failure. It
does not belong on this list.)

**Observed firing, 2026-08-11 → 2026-08-15 (7):**

| What | Evidence |
| --- | --- |
| `refs/deploy/beast-01` sat 4 commits stale; the pin workflow ran **green** | ref at `69e411d` while `main` was `d97a3c8` |
| `beast-slam-save` printed "not running, nothing to save" and **exited 0**, twice, with pid 5392 up | two consecutive contradictory invocations |
| slam_toolbox alive, `/map` publishing at 0.2 Hz, map frozen at 237×54 | `use_sim_time:=true` leak from `online_async_launch.py` |
| `map→odom` bit-identical for 3 minutes while wheel odometry accumulated ~1.0 m | destroyed the map; costmap wedged at cost 99 under the robot |
| Robot still turning **0.335 rad/s ten seconds** after the command ended | `odom→base_link` yaw samples |
| Three scripts in `/usr/local/sbin` older than the repo, fixes merged, deploy reported success | md5 diff vs `deploy/bin` |
| 17 units byte-identical to git; enablement exists only as symlinks | `systemctl` vs repo |

**Read in code, not observed (2):** `ros2 launch` returns 0 on child node death (`return_code`
set only on exception paths; ros2/launch#666 open, PR #712 unmerged); the verify's gate restore
re-arms on any `gate_before` that is not literally `false`, including the empty string a timed-out
read returns.

**Recorded by a prior session (1):** `deploy-to-beast.sh` built new code, aborted before
restarting, and left `git rev-parse HEAD` reading correct.

## How it got here — a port, not a drift

| Date | Event |
| --- | --- |
| 2025-11-25 → 2026-07-10 | Waveshare `ugv_ws` (`037dfca`, DUDULRX). **Zero `deploy/`, zero systemd files** — verified against the tree |
| **2026-07-11** | First Coldaine commits: *Adapt Beast bringup for **Jetson** safety*, *Support **JetPack** OpenCV* — and `3777a09` **"Add disabled Jetson base service"** |
| 2026-08-03 | `1e8a167` subtree-merges the fork into RobotOverview |
| 2026-08-07 | `7e86feb` *"strip automatic safety apparatus + cmd_vel watchdog"* |
| 2026-08-10 | Cockpit outage → **8 deploy files in one day** |
| 2026-08-14 | Autonomy on-ramp → **13 deploy files in one day** |

122 commits (70 pre-import, 52 post). Of the 52 touching `robot/`, **33 touch `deploy/`** — a
directory the vendor never shipped, containing none of the robot's behaviour.

Day one was a **hardware port**: Raspberry Pi → Jetson Orin Nano Super 8GB (JetPack 6.2.2 /
R36.5). Waveshare's SD image and their `/home/ws/ugv_ws` container path both stopped applying,
silently transferring three responsibilities to us — **the environment**, **the supervision**,
and **the deploy**. The port was correct; you want the compute. The sentence that should have
followed it never got said, so each responsibility was improvised on the day it first drew blood.
Read the script headers in birth order and they admit it: *"Installed 2026-08-10 after the
cockpit outage"*, *"after Wi-Fi dark period"*, *"added AFTER a posegraph explosion destroyed the
only good map"*.

Each script is one of the vanished operator's jobs. **1,870 lines of bash is the price of the
missing human.**

## The preserve list

**This is the whole plan.** Everything in `robot/beast/ros2_ws` — 51,361 lines, 32 packages, 13
scripts, 17 units — is deleted at Phase D unless it appears below. Survival requires a reason,
and "it currently works" is not one.

### Code that survives (1 package)

| Item | Lines | Why it survives |
| --- | ---: | --- |
| `beast_power` | 1,168 | Genuinely ours and genuinely differentiated: INA219 on `i2c-7`, load-compensated SoC (`OCV = V + |I|·0.14Ω`), durable CSV logging with interrupted-tail repair, datasheet-vector tests. No upstream package does this for this hardware. |

That is the list. One package, 1,168 lines, out of 51,361.

**`ugv_cockpit` (800) needs an owner decision, not a default.** It is the Hangar bridge, and the
Hangar app is a separately deployed surface. Either it moves to the app side or it is rebuilt as
a thin `rosbridge` config. It should not survive by inertia into a robot workspace.
Comment on lines +116 to +118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate deletion on a working cockpit replacement

This records an unresolved owner decision but never makes its resolution a phase gate or done condition; Phase A forbids copying unnamed code, while Phase D deletes ugv_cockpit with the rest of the workspace. The current Hangar browser and server clients both depend on this package's loopback rosbridge, topic allowlists, and motion service, so the plan can pass C1 by driving locally and still take the product's telemetry and controls offline. Require a deployed, end-to-end-verified replacement before deletion rather than leaving the bridge outside the execution contract.

AGENTS.md reference: AGENTS.md:L60-L68

Useful? React with 👍 / 👎.


### Knowledge that survives (extracted to artifacts, not code)

Expensive to re-derive, invisible in a diff, and the actual reason a rebuild is safe:

1. **Nav2 tuning rationale** — RotationShim wrapping RegulatedPurePursuit, `allow_reversing:
false`, `use_rotate_to_heading: true`, effective speed cap living in the velocity smoother
(`[0.26, 0.0, 1.0]`) not RPP's `desired_linear_vel`. That shape is deliberate for a robot with
a 104° blind wedge astern. Regenerating defaults silently loses it.
Comment on lines +124 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve and test the collision-monitor safety envelope

The preserve entry names the controller wrapper, reversing behavior, and speed cap, but omits the active collision-monitor contract in ugv_nav/params/rpp.yaml: the cmd_vel_smoothedcmd_vel_nav chain, stop/slow polygons, scan source, and stale-source timing. Under Phase A's rule that unnamed behavior cannot enter the new tree, Phase B can regenerate Nav2 parameters without this final obstacle stop layer, while C1 still passes because it only checks that the robot drives. Preserve the exact collision-monitor wiring and envelope and require obstacle plus stale-scan stop tests before cutover.

Useful? React with 👍 / 👎.

2. **ESP32 T-code protocol** — `T:13` drive, `T:131` startup enable (unverified), `T:132` LED,
`T:133` pan-tilt, `T:137`, `T:3` OLED, `T:900` model, inbound `T:1001` at 20 Hz. The input to
the `ros2_control` hardware interface.
Comment on lines +128 to +130

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve IMU and encoder feedback from T:1001

The preserved protocol knowledge reduces inbound T:1001 to its type and frequency, but the current frame is also fanned out by beast_base as imu/data and odom/odom_raw, which feed the wheel odometry and EKF configuration that this plan deletes. Phase B only says to turn the frame into joint states and does not specify the remaining fields, conversions, covariances, or equivalent ROS outputs. The rebuilt robot can therefore pass the C1 driving check while silently losing wheel/IMU localization inputs and producing unusable maps or poses. Preserve the complete feedback contract and test the resulting odometry and IMU topics.

Useful? React with 👍 / 👎.

Comment on lines +128 to +130

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebuild the non-drive T-code controls

Although the knowledge list names T:132, T:133, T:3, and T:900, it does not preserve their payload schemas, ROS topic/service contracts, or any Phase B implementation beyond the drive-only beast_hardware interface. Deleting beast_base therefore removes the LED controls, pan-tilt commands, OLED updates, and model-selection startup command, while C1's drive-only gate and the done conditions still pass. Specify replacement interfaces and end-to-end checks for these user-visible controls, or explicitly scope their removal through the product rather than silently dropping them.

AGENTS.md reference: AGENTS.md:L60-L63

Useful? React with 👍 / 👎.

3. **twist_mux ladder** — `cmd_vel_joy_robot` 150 > `cmd_vel_joy_operator` 100 > `cmd_vel_ui` 50
> `cmd_vel_nav` 10; `locks.estop` at 255 with `timeout: 0.0`. Including the property that
expiry only *removes* a source — it never emits a zero.
4. **Hardware paths** — ESP32 at `/dev/serial/by-id/usb-1a86_USB_Single_Serial_5B5E130201-if00`
(**not** `/dev/ttyTHS1`, which docs claimed for weeks), LiDAR by-id, INA219 on `i2c-7`, udev
`03e7` for the OAK-D, groups `dialout i2c video render docker`.
Comment on lines +134 to +136

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the complete LiDAR configuration

The preserve list records only “LiDAR by-id,” omitting the actual device path, model, fixed-bin count, direction/crop settings, and calibrated blind-band values currently carried by ugv.env.example and the launch configuration that Phase D deletes. C1 can still pass by inheriting the current robot's existing /etc/beast/ugv.env, while C2 cannot exercise the sensor, so a fresh host can start with the wrong device/default scan geometry or include chassis self-returns. Phase A should retrieve and identify the complete current LiDAR record in Hangar, then make those values and a scan-geometry check explicit rebuild inputs.

AGENTS.md reference: AGENTS.md:L5-L17

Useful? React with 👍 / 👎.

5. **Pack limits** — 8.332 V measured hard trip, ~9.6 V operational floor, 0.14 Ω internal

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Pack limit value does not match recorded DB measurement

AGENTS.md / ins-beast-pack-cutoff-measured records 8.368 V as the measured hard cutoff, with ~9.6 V as the operational floor. This plan states 8.332 V and says "Never re-derive these," but the value differs from the recorded measurement by 36 mV. For a hardware safety threshold that gates operational decisions, accuracy matters — reconcile against the DB before treating it as authoritative.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

resistance, charger 12.6 V / 2 A. Never re-derive these; an invented 9.9 V floor already
aborted a live test with ~12 minutes of reserve left.
6. **The traps** — the "Watch out for" section below, carried forward verbatim.

Deliverable: these land as a Hangar briefing (knowledge belongs in the DB, per AGENTS.md), and
the params files in the new tree carry (1) and (3) with the rationale in comments.

### Everything else is deleted

Named so nobody has to guess: all 13 `deploy/bin` scripts, all 17 systemd units, the entire
vendored fork including `ugv_nav` (1,550 lines of `nav2_bringup` launch files copied verbatim
with Intel copyright headers intact, 92-line delta), `beast_base` (whose `base_ctrl.py` is
byte-identical upstream and whose job becomes the `ros2_control` hardware interface),
`ugv_bringup`, `ugv_slam`, `ugv_tools`, `ugv_vision`, `cartographer`, and the ~28,000 lines of
never-launched vendor packages.

## Done condition

1. A fresh Orin, flashed and given this repo, reaches a verified-running BEAST-01 with **no
undocumented step**.
2. `git` states which units are enabled; changing that set is a reviewed diff, not an SSH session.
3. A ROS node dying makes its systemd unit fail. `systemctl is-active` is admissible evidence.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Check lifecycle activation, not only process state

For the direct-node design, systemctl is-active is not sufficient evidence for Nav2: the checked ugv_nav/launch/nav_bringup/navigation_launch.py explicitly manages controller, planner, behavior, smoother, navigator, and waypoint nodes through ROS lifecycle transitions, and a configuration or activation failure can leave those processes alive while inactive. C1 only requires driving, so the plan can declare supervision fixed while navigation remains unusable behind green systemd units. Require all expected lifecycle nodes to reach active and make lifecycle-manager/configuration failure fail the relevant unit.

Useful? React with 👍 / 👎.

4. The workspace builds from a pinned upstream manifest plus `beast_power`.
5. `robot/beast/ros2_ws` is **deleted** — not parked, not ignored, deleted.
6. `deploy/bin` no longer exists as a concept: no supervisor scripts, no verify script, no
privilege wrapper. If any survives, it carries a written reason a standard mechanism could
Comment on lines +160 to +163

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace the robot deployment path before deleting it

The current gated workflow advances refs/deploy/beast-01 for beast-pull.timer, but this plan deletes that pull agent and every other deploy script while Phase B provides only initial provisioning. No replacement consumes later approved commits, rebuilds the workspace, restarts it, or reports rollback state. After Phase D, the pin workflow can remain green while BEAST-01 stays on old code, immediately recreating the repository-versus-robot drift diagnosed by this plan. Add and verify a standard ongoing deployment mechanism before removing the existing consumer.

AGENTS.md reference: AGENTS.md:L65-L72

Useful? React with 👍 / 👎.

not do its job.

## Phases

### A — Extract the preserve list *(offline + robot reads; nothing changes)*

Produce the preserve list above as real artifacts: the knowledge briefing landed in Hangar, and
a short repo document naming exactly what Phase B may copy. **Nothing may be copied into the new
tree that is not named here.** This is the only gate between the old tree and the new one.

Done when: a reader who has never seen the old tree can build the new one from this list.

### B — Build the new tree, clean, offline

New workspace root. Empty. Every file typed on purpose:

- `beast.repos` — pinned upstream manifest (slam_toolbox, nav2, ldlidar, rf2o,
Comment on lines +176 to +180

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Name the replacement workspace root

Phase B says only “New workspace root” while Phase D deletes the repository's sole named robot workspace. The new systemd ExecStart paths, sparse checkout, provisioning, deployment, and operator documentation all require one stable repo-relative location; without it, an executing agent must invent a path and could even place the replacement beneath the directory Phase D removes. Name the destination explicitly and carry that path through every install and close-out deliverable.

AGENTS.md reference: AGENTS.md:L65-L68

Useful? React with 👍 / 👎.

robot_localization, depthai, twist_mux, joy, teleop_twist_joy). Several already come from apt.
- `beast_power` — the one carried package.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Provision the durable power-log storage path

On a fresh Orin, carrying beast_power alone does not preserve its advertised durable CSV logger: PowerLogger writes /data/beast/power/power-log.csv, while the current deploy/storage/install.sh is what creates /data and gives beast a writable subtree. Phase D deletes that installer, and C1 runs on the already-prepared robot, so the logger can pass unnoticed there but fail with PermissionError when DurableCsvWriter.__init__ tries to create the path on a clean host. Make the directory ownership, logger unit ordering, and a real write check explicit provisioning requirements.

Useful? React with 👍 / 👎.

- `beast_hardware` — **new code**: a `ros2_control` `SystemInterface` speaking the T-code
protocol (parse inbound `T:1001` at 20 Hz → joint states; emit `T:13` on write). This is the
Comment on lines +180 to +184

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve a BEAST robot description

Phase B's exhaustive new-tree list has no robot-description/URDF artifact, even though Phase D deletes ugv_description. The current bringup loads that package to publish the base_footprint/base_link, LiDAR, camera, and wheel transforms, and a ros2_control SystemInterface also needs its joints and hardware declaration. The listed drivers, Nav2 parameters, and beast_hardware therefore cannot form a valid TF/controller graph after deletion, so C1 cannot reach SLAM or navigation. Add a deliberately rebuilt BEAST description and validate the required TF tree and controller interfaces before cutover.

Useful? React with 👍 / 👎.

riskiest item in the plan; see below.
Comment on lines +183 to +185

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the startup stop and motion gate

When Phase B replaces beast_base, the specified beast_hardware behavior only parses T:1001 and emits T:13; it omits the unconditional startup zero and the latched /ugv/set_allow_motion gate currently implemented in beast_base/base_node.py. Those behaviors are load-bearing because the ESP32 retains its last velocity, mux-source expiry emits no zero, and the browser's DISARM action depends on that service. Since C1 only requires the replacement to drive, this plan can pass its gates while allowing a pre-restart command to remain latched and removing remote disarm. Add both safety behaviors and their acceptance tests to the preserve contract.

Useful? React with 👍 / 👎.

- `params/` — nav2, twist_mux, slam_toolbox, carrying the preserved tuning with rationale in
comments. `use_sim_time` explicitly `false`, in the params file, not a launch argument.
Comment on lines +186 to +187

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve automatic map serialization before cutover

In the checked Phase B/D flow, the replacement carries SLAM parameters but deletes the only mechanism that serializes the live posegraph and occupancy grid: beast-slam.service invokes deploy/bin/beast-slam-save on stop, including the size guard and backup that prevent a bad live map from overwriting the last usable copy. Since C1 only has to drive, the plan can pass its gates and then discard every mapping session at shutdown, leaving no map for saved-map navigation. Add the save/resume/backup contract and an end-to-end persistence test to Phase B/C.

Useful? React with 👍 / 👎.

- `systemd/` — units written fresh: direct node `ExecStart`, no `bash -lc`, no `ros2 launch`
wrapper, no `source` chain. Enablement declared in the repo.
- `provision.sh` — written **from the preserve list**, not from the robot. `--check` mode.

No robot required for any of this.

**Fallback on the risky item:** if `beast_hardware` is not ready when the rest is, carry
`beast_base` as an explicitly temporary shim, tracked as a debt item with a written expiry — not
absorbed silently into the preserve list.

### C — Prove it, twice, because one test cannot prove both things

**C1 — functional.** Bring the new tree up on the current robot at a different workspace path,
with differently-named units, nothing enabled. Proves the stack works. **Cannot prove the
Comment on lines +200 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Isolate the old hardware stack during C1

During C1, differently named systemd units do not isolate the replacement from the running old ROS graph: both stacks still target the same ESP32 serial device and command topics, so leaving beast-ros-base active can produce competing T-code readers/writers and make observed motion impossible to attribute to the new stack. The plan never requires stopping the old serial/controller owner before this test, despite otherwise promising that the old stack remains available until cutover. Add an explicit stop-and-verify-exclusive-ownership procedure with a bounded rollback before permitting any C1 motion.

Useful? React with 👍 / 👎.

declaration is complete** — that machine already has everything, including every undeclared
accident.

**C2 — declaration completeness.** Run `provision.sh` somewhere that does *not* already have
everything: a container on the Jetson from the JetPack base image, or a fresh flash. Every
missing apt package, pip install and env assumption surfaces here, loudly. Note the limit
honestly: a container will **not** catch udev rules or group memberships, so those need a fresh
flash or explicit inspection.
Comment on lines +205 to +209

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require a real boot for declaration-completeness proof

When C2 uses the explicitly permitted JetPack container instead of a fresh flash, it cannot exercise host systemd installation, unit enablement, boot ordering, udev, groups, Tailscale, or the cold-boot clock behavior. C1 does not cover those either because its differently named units remain disabled on an already provisioned robot. The plan can thus declare C1/C2 complete without ever testing the primary failure mode it identifies—reconstructing the boot-enabled machine state from Git—and a fresh Orin may still fail the top-level done condition. Require provisioning and rebooting a fresh host, or an equivalent host-level image test, before Phase D.

Useful? React with 👍 / 👎.


Done when: C1 drives, and C2 comes up from nothing.

### D — Cut over, then delete in one commit

Switch the robot to the new tree. Then delete `robot/beast/ros2_ws` entirely — 51,361 lines, 13
scripts, 17 units — in a single commit. **Deleted, not migrated, not parked.** Git history is the
archive; that is what `docs/plans/README.md` already says about plans and it applies here.
Comment on lines +215 to +217

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Park unused vendor packages instead of deleting them

Phase D explicitly deletes the whole vendored workspace, including the never-launched packages named earlier in the plan. That conflicts with the repository's required treatment for vendor packages we do not run: retain them with COLCON_IGNORE and remove them from both build allowlists. Following this work order would therefore produce an unacceptable cleanup even if the replacement stack worked; restructure the cutover so those packages remain parked.

AGENTS.md reference: AGENTS.md:L78-L82

Useful? React with 👍 / 👎.


### E — Close out

Delete this plan (executed plans are deleted, not archived). Update `docs/beast-ops.md` Quick
connect, `docs/deploy.md`, and `README.md`. Retire the strip-down and vendored-surface plans,
whose subject no longer exists.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: "no longer exists" describes a future state in present tense

Phase E runs after the rebuild completes. The vendored workspace still exists until Phase D deletes it. "No longer exists" implies it is already gone; use future tense ("will no longer exist") to match the plan's sequence and avoid suggesting the workspace has already been removed.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


## Watch out for

Verified this session. Carried into Phase A as preserved knowledge.

1. **`cartographer` is not Google Cartographer.** A 31-line Waveshare package that name-shadows
it, alive only because `ugv_nav/localization_launch.py` calls
`get_package_share_directory('cartographer')` **unconditionally**. Irrelevant after D, but it
will bite anyone who touches the old tree first.
2. **Four dependencies work only by accident** — `ugv_bringup`→`robot_localization`,
`ugv_tools`→`joy`, `ugv_slam`→`slam_toolbox`/`cartographer_ros`/`rtabmap`, none declared in
`package.xml`. C2 is designed to surface exactly this class.
3. **Tuned params look like config but are knowledge.** See preserve item (1). The single most
losable thing in the rebuild.
4. **`use_sim_time` defaults to `true`** in `online_async_launch.py` and is applied *after* the
params file, overriding it. It froze the map for a full session. Any design that keeps a launch
file re-imports this.
5. **`ros2 launch` returns 0 on child node death.** The root cause of the supervision gap; the
reason B uses direct nodes.
6. **No `cmd_vel` watchdog exists** (D8, 2026-08-07). Never command motion from a foreground SSH
process. Any test must send explicit zeros — ceasing to publish is not stopping. Measured:
0.335 rad/s ten seconds after the command ended.
7. **In-place rotation corrupts the map.** Do not use "slow spin for coverage" as a mapping
prerequisite; it froze `map→odom` for three minutes and wedged the costmap. Prefer translating
survey motion and verify `map→odom` is actually moving.
8. **Wheel travel is ~55–60% of commanded** — open-loop rotation angle is untrustworthy
independently of (7). Relevant to calibrating `diff_drive_controller`.
9. **`beast-ctl` must stay root-owned in `/usr/local/sbin`** for as long as it exists.
`/home/beast` is writable by `beast`, so allowlisting a path under it is a trivial root
escalation. It must never be executed from the repo checkout.
10. **The install path cannot install.** `deploy-to-beast.sh`'s `install_bin_if_needed` falls
through to a break-glass password no unattended path can supply, prints
`WARN cannot passwordless-install`, and **continues successfully**. Three scripts are stale on
the robot right now because of it.
11. **The robot has no RTC battery** (Orin Nano carrier leaves J3 unpopulated). It cold-boots at
epoch 0 until NTP wins — ~100 s of 1969 timestamps at the 2026-08-14 boot, which killed
`beast-cockpit-serve` outright. Unit ordering in B must account for it; `wl-ds3231-rtc` is the
recorded hardware fix.

## Anti-goals

These constrain *how* the rebuild happens. None is a reason to preserve more.

- **Nothing is deleted before its replacement runs on hardware.** The robot drove autonomously on
2026-08-15 and must keep driving. This bounds sequence, not scope — Phase D deletes everything
regardless of how comfortable the old tree feels by then.
- **Do not count inert code as progress.** The ~28,000 SLOC of never-launched vendor packages
cost nothing — `beast-pull` does not even rebuild them. They vanish with the tree at D. That is
a side effect, never an achievement, and must not be reported as one.
- **No config-management framework** for one robot. The bug is *undeclared*, not
*not-declared-in-Ansible*.
- **No script #17.** If the answer to any phase is "write another supervisor," the phase is
wrong. The new tree ends with fewer moving parts than the old one, or it has failed.
- **The preserve list does not grow during execution.** Additions require the same written
justification as the original entries, recorded in the plan. "We found we still needed it" is
the exact mechanism that built the current system.
1 change: 1 addition & 0 deletions docs/plans/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ disagree, the code is right and the plan is stale.

| Plan | What it covers | Blocking? |
| --- | --- | --- |
| [BEAST-01 platform rebuild](2026-08-15-beast-platform-rebuild.md) | **Governing plan — clean-room rebuild, not a migration.** The repo is not the authority on the robot and nothing reports when it is wrong: enablement and 17 machine facts exist only on one NVMe, and 7 mechanisms were observed reporting success while failing. `robot/beast/ros2_ws` (51,361 lines, 13 scripts, 17 units) is **deleted**; the preserve list is one package (`beast_power`) plus extracted knowledge. Phases: extract → build clean offline → prove twice → cut over and delete. Retires the strip-down and vendored-surface plans, whose subject ceases to exist. | Not blocking. Phase A gates everything: nothing may enter the new tree that is not on the preserve list |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Present tense "is deleted" misleadingly implies the vendored workspace is already gone; description also contradicts lines 16–17

robot/beast/ros2_ws still exists — this plan will delete it in Phase D, but "is deleted" reads as current state. The description also claims this plan "Retires the strip-down and vendored-surface plans," yet lines 16–17 of this same README still list those plans as live work. Use future tense ("will be deleted") and resolve the contradiction by marking the retired plans as superseded or removing them.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

| [BEAST-01 Command Deck + sensor fusion](2026-07-31-beast-command-deck-plan.md) | Historical cockpit context. Its robot-side safety-spine work is superseded by the 2026-08-07 strip-down; the Hangar cockpit remains product scope. | Superseded for robot-side work |
| [BEAST ROS 2 strip-down](2026-08-07-beast-ros-drift-inventory-and-stripdown.md) | Remaining BEAST ROS 2 custom-drift strip after #174: Phase 1 extracted `beast_base` from the vendor `ugv_bringup.py` and removed `/ugv/watchdog_state` consumers (**done, #176**); Phase 2 deletes vizanti + `ugv_web_app` and reverts the 12 demo retargets; Phase 3 drift audit + robot ground truth, then the plan is deleted. | Phase 2 partial (H2 neutralized, not deleted); Phase 1 extraction done (#176) |
| [Finish the wiring model](2026-07-30-wiring-model-completion.md) | One spine, two eyes: The Board consumes `wiring.ts`, corpus extraction (schematics, firmware, photos, CAD), facts landed with zone citations, operator answers on screen. Merges the 2026-07-27 unification, extraction, and CAD plans. | Q1/Q2 safety-relevant (wrong 40-pin numbering puts 5 V into a Jetson UART pin); X1 gates drilling |
Expand Down
Loading