diff --git a/WATCHDOG.md b/WATCHDOG.md index f6fde14..839100d 100644 --- a/WATCHDOG.md +++ b/WATCHDOG.md @@ -160,6 +160,11 @@ identical to the register. | CONFIG-IDS | recorded | *mast_id is replaced by the column, base_plate, head_pitch and base_tilt IDs*: `supersed (superseded, supersedes)`, `replac`, `historical`, `legacy`, `no longer`
*the head_pitch baseline is hp30*: `supersed (superseded, supersedes)`, `historical`, `was` | | NO-SUSPENSION | recorded | `3.0`, `no`, `not fitted`, `without` | | DRIVE-TRACK | open | not scanned until the decision is taken | +| UI-MANUAL-VELOCITY-INPUT | open | not scanned until the decision is taken | +| STATUS-VOCABULARY | recorded | `not`, `never`, `only`, `hardwired`, `STOPPED \(SOFTWARE\)` | +| STOP-SCOPE | recorded | *E-stop means only the hardwired buttons; the UI control is Stop (software), a functional stop*: `not`, `never`, `no`, `supersed (superseded, supersedes)`, `historical`
*Stop (software) covers base motion only today and is labelled base-only*: `target`, `planned`, `future`, `will`, `not`, `base[- ]only`
*Stop (software) is a functional stop, not a safety function*: `not`, `never`, `functional` | +| OPERATOR-PANEL | recorded | *the operator panel is the Getac ZX80, 8 inch, 1920 x 1200, landscape*: `supersed (superseded, supersedes)`, `replac`, `historical`, `earlier`, `previous`, `was`
*the ZX80 operator panel is used in landscape*: `not`, `never` | +| UI-GATE-A | recorded | `partial`, `A1`, `supersed (superseded, supersedes)`, `historical`, `was`, `6 Nov` | ## Approved public contacts diff --git a/decisions.yaml b/decisions.yaml index c2ecbf0..61d5c1c 100644 --- a/decisions.yaml +++ b/decisions.yaml @@ -53,12 +53,21 @@ schema_version: 1 in_force: - source: P-03-rev19.6 - date: 2026-10-18 + source: P-03-rev20.1 + date: 2026-10-30 sources: + P-03-rev20.1: + title: "OpenAMRobot 2.0 Decision Addendum rev20.1, with the platform lead's decisions of 30 October 2026 (the addendum in force; carries the rev20.0 items 15, 17 and 18 head-camera values; item 24: whole chest layout, including the ZX80 operator panel correction of 28 October 2026)" + evidence: plan-set document, not held in any repository; provenance only, CI never reads it + Alex decision 2026-10-30: + title: Platform decision of 30 October 2026 (UI manual velocity input and stop lock, status vocabulary, stop scope, UI Gate A date); change request openAMRobot/.github#72 + evidence: decision record, not held in any repository; provenance only, CI never reads it; recorded with P-03 rev20.1 + E-01-rev1.5: + title: E-01 Safety and Power Distribution Baseline rev1.5 ("E-stop" means only the hardwired buttons) + evidence: plan-set document, not held in any repository; provenance only, CI never reads it P-03-rev20.0: - title: "OpenAMRobot 2.0 Decision Addendum rev20.0, 27 October 2026, with the 28 October 2026 corrections (items 15, 17 and 18: head-camera lens 42 mm ahead of the shoulder, baseline pitch 30 degrees, hp30; item 24: second E-stop on the static lift column)" + title: "OpenAMRobot 2.0 Decision Addendum rev20.0, 27 October 2026, with the 28 October 2026 corrections (earlier revision; superseded by rev20.1 as the addendum in force; items 15, 17 and 18: head-camera lens 42 mm ahead of the shoulder, baseline pitch 30 degrees, hp30; item 24: second E-stop on the static lift column)" evidence: plan-set document, not held in any repository; provenance only, CI never reads it Alex decision 2026-10-20: title: Platform decision of 20 October 2026 (TF-Luna cliff pitch 10 degrees; WCM-500-24 on release 001 and 2.0; embodied-AI repository; platform-sw pin for the combined model) @@ -83,7 +92,7 @@ sources: title: P-03 Decision Addendum, revision 18.4 (earlier revision) evidence: plan-set document, not held in any repository; cited by item P-03-rev19.6: - title: "OpenAMRobot 2.0 Decision Addendum rev19.6, 18 October 2026 (the addendum in force; item 22: Gigavac GX11CAC contactors and the item 22 controller I/O)" + title: "OpenAMRobot 2.0 Decision Addendum rev19.6, 18 October 2026 (earlier revision; superseded by rev20.1 as the addendum in force; item 22: Gigavac GX11CAC contactors and the item 22 controller I/O)" evidence: plan-set document, not held in any repository; cited by item E-01-rev1.3: title: E-01 Safety and Power Distribution Baseline rev1.3, 18 October 2026 (detail for P-03 rev19.6 item 22) @@ -661,7 +670,7 @@ decisions: unit: none date: 2026-10-28 review_by: 2026-12-09 - source: {document: P-03-rev20.0, item: "items 15 and 18 (28 Oct 2026 corrections); camera identity per P-03-rev18.7 item 15"} + source: {document: P-03-rev20.1, item: "items 15 and 18 (values of the P-03-rev20.0 28 Oct 2026 corrections, unchanged); camera identity per P-03-rev18.7 item 15"} supersedes: - {value: "lens 0.022 m ahead of and 0.223 m above the arm mount point", source: P-03-rev18.9 item 18} - {value: "pitch 15 to 35 degrees down in 5 degree steps, baseline 25 degrees", source: P-03-rev18.7 item 15} @@ -673,10 +682,10 @@ decisions: message: the head camera is the Stereolabs ZED Mini, SKU ZED-121210 (P-03 rev18.7 item 15) - pattern: '(?P\b(?:0\.022 m|22 mm) ahead\b)' unless: 'supersed|historical|\bwas\b' - message: the head-camera lens is 0.042 m (42 mm) ahead of the arm mount point (P-03 rev20.0 item 18) + message: the head-camera lens is 0.042 m (42 mm) ahead of the arm mount point (P-03 rev20.1 item 18) - pattern: '(?P\b(?:head[- ]camera|ZED Mini)\b[^\n]{0,60}\bpitch\b[^\n]{0,40}\bbaseline:?\s*25\b(?!\s*mm))' unless: 'supersed|historical|\bwas\b' - message: the head-camera baseline pitch is 30 degrees down (P-03 rev20.0 item 15) + message: the head-camera baseline pitch is 30 degrees down (P-03 rev20.1 item 15) verification: machine: another ZED model (ZED 2, ZED 2i, ZED X, ZED X Mini) without a superseded or legacy label; the lens 0.022 m or 22 mm ahead; a head-camera pitch baseline of 25 degrees human: {reviewer: platform-lead, evidence: supplier SKU on the OpenArm 2.0 set and the lift-carriage mount drawing with the pitch steps} @@ -1270,7 +1279,7 @@ decisions: unit: none date: 2026-10-28 review_by: 2026-12-09 - source: {document: P-03-rev20.0, item: "item 17 (28 Oct 2026 corrections: head_pitch baseline hp30); column, base_plate and base_tilt per P-03-rev18.9 items 17 and 18"} + source: {document: P-03-rev20.1, item: "item 17 (head_pitch baseline hp30, value of the P-03-rev20.0 28 Oct 2026 corrections, unchanged); column, base_plate and base_tilt per P-03-rev18.9 items 17 and 18"} supersedes: - {value: "head_pitch hp15, hp20, hp25, hp30, hp35 (baseline hp25)", source: P-03-rev18.9 item 17} - {value: "mast_id (mast_1300 to mast_1450)", source: P-03-rev18.2 shoulder height} @@ -1281,7 +1290,7 @@ decisions: message: mast_id is replaced by the column, base_plate, head_pitch and base_tilt IDs (P-03 rev18.9 item 17) - pattern: '(?P\bhead_pitch\b[^\n]{0,60}\bbaseline:?\s*[`"]?(?:hp)?25\b|\bbaseline:?\s*[`"]?hp25\b)' unless: 'supersed|historical|\bwas\b' - message: the head_pitch baseline is hp30 (P-03 rev20.0 item 17) + message: the head_pitch baseline is hp30 (P-03 rev20.1 item 17) verification: machine: mast_id used as a current configuration ID; a head_pitch baseline of hp25 human: {reviewer: software-lead, evidence: configuration hash inputs and dataset metadata carrying the four IDs} @@ -1327,3 +1336,149 @@ decisions: machine: none while open human: {reviewer: platform-lead, evidence: final hub-motor bracket design} owner: platform-lead + + - id: UI-MANUAL-VELOCITY-INPUT + title: "Proposed: UI manual velocity input and stop lock interface (source PR openamr-platform-sw #90 still open)" + summary: "Proposed: /ui/cmd_vel TwistStamped into twist_mux manual; /safety/stop_lock released only by stop_lock_release." + fix_hint: "Not scanned while Proposed; use /ui/cmd_vel (TwistStamped) and release the lock only via /safety/stop_lock_release." + kind: configuration + status: open + values: + - "Proposed until openamr-platform-sw #90 (head 710c98bc) merges and the owner accepts it; stored as status open because the schema has no Proposed status" + - UI manual velocity input /ui/cmd_vel, geometry_msgs/TwistStamped, into the twist_mux manual input + - stop lock state /safety/stop_lock, std_msgs/Bool, QoS reliable and transient local + - engaged by /safety/stop_lock_engage and released only by /safety/stop_lock_release, both std_srvs/Trigger + unit: none + date: 2026-10-30 + review_by: 2026-12-11 + source: {document: Alex decision 2026-10-30, item: "UI manual velocity input and stop lock; implementation openamr-platform-sw #90 head 710c98bc (open); P-03-rev20.1"} + applies_to: + repositories: ["openamrobot-ui", "openamr-platform-sw", "openamrobot-docs"] + files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.py", "**/*.ts", "**/*.tsx", "**/*.js"] + check: + - pattern: '(?P/ui/cmd_vel\b[^\n]{0,60}\bgeometry_msgs/(?:msg/)?Twist\b|\bgeometry_msgs/(?:msg/)?Twist\b[^\n]{0,60}/ui/cmd_vel\b)' + unless: 'supersed|historical|legacy|\bnot\b|\bwas\b' + message: /ui/cmd_vel carries geometry_msgs/TwistStamped, not unstamped Twist (Alex decision 2026-10-30) + - pattern: '(?P/safety/stop_lock_engage\b[^\n]{0,60}\breleas\w*|\breleas\w*[^\n]{0,60}/safety/stop_lock_engage\b)' + unless: 'stop_lock_release|\bonly\b|\bnot\b|never' + message: the stop lock is released only by /safety/stop_lock_release (Alex decision 2026-10-30) + - pattern: '(?P/safety/stop_lock\b(?!_)[^\n]{0,60}\b(?:best[ _-]?effort|volatile)\b)' + unless: '\bnot\b|never|supersed' + message: /safety/stop_lock is reliable and transient local (Alex decision 2026-10-30) + verification: + machine: none while open; when recorded, unstamped Twist on /ui/cmd_vel, the engage service described as releasing the lock, a best-effort or volatile /safety/stop_lock + human: {reviewer: software-lead, evidence: "merged openamr-platform-sw #90 with ros2 topic info -v showing /safety/stop_lock reliable and transient local, and the UI publishing TwistStamped on /ui/cmd_vel into the twist_mux manual input"} + owner: software-lead + + - id: STATUS-VOCABULARY + title: Robot status and UI presentation state vocabulary + summary: "8 robot states; SAFETY_STOPPED only from the hardwired chain; UI adds DISCONNECTED, UNKNOWN, STALE, STOPPED (SOFTWARE)." + fix_hint: "Derive SAFETY_STOPPED only from the hardwired chain diagnostic; show a software stop as STOPPED (SOFTWARE)." + kind: distinction + status: recorded + values: + - robot states READY, EXECUTING, WAITING, BLOCKED, RECOVERING, NEEDS_ASSISTANCE, SAFETY_STOPPED, FAULTED + - UI presentation states DISCONNECTED, UNKNOWN, STALE, STOPPED (SOFTWARE); the UI derives them and they are not robot states + - SAFETY_STOPPED comes only from the hardwired safety chain's diagnostic signal, never from the software stop, the stop lock, a watchdog or a timeout + unit: none + date: 2026-10-30 + review_by: 2026-12-11 + source: {document: Alex decision 2026-10-30, item: "status vocabulary; P-03-rev20.1"} + applies_to: + repositories: ["openamrobot-ui", "openamr-platform-sw", "openamr-upperbody-sw", "openamrobot-docs"] + files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.py", "**/*.ts", "**/*.tsx", "**/*.js"] + check: + - pattern: '(?P\bSAFETY_STOPPED\b[^\n]{0,60}\b(?:stop_lock|software stop|Stop \(software\)|watchdog|timeout|heartbeat)\b|\b(?:stop_lock|software stop|Stop \(software\)|watchdog|timeout|heartbeat)\b[^\n]{0,60}\bSAFETY_STOPPED\b)' + unless: '\bnot\b|never|\bonly\b|hardwired|STOPPED \(SOFTWARE\)' + message: SAFETY_STOPPED comes only from the hardwired chain's diagnostic signal; a software stop is shown as STOPPED (SOFTWARE) (Alex decision 2026-10-30) + verification: + machine: SAFETY_STOPPED derived from the stop lock, a software stop, a watchdog, a timeout or a heartbeat + human: {reviewer: platform-lead, evidence: "status mapping source in platform-sw and the UI showing SAFETY_STOPPED driven only by the hardwired chain diagnostic input; the hardwired safety-chain test record, never a software PASS"} + owner: platform-lead + + - id: STOP-SCOPE + title: Software stop scope and the meaning of E-stop + summary: "Stop (software) is a robot-side functional stop, base-only today; E-stop means only the hardwired buttons." + fix_hint: "Label the software stop Stop (software), base-only; keep E-stop for the hardwired buttons only." + kind: distinction + status: recorded + values: + - "\"Stop (software)\" is a robot-side functional stop, not a safety function" + - today it covers base motion only and is labelled base-only + - "target: one robot-side stop for base, arms and lift, designed by the platform lead and the safety owner (not yet designed)" + - "\"E-stop\" means only the hardwired buttons (E-01 rev1.5); see ESTOP-SCOPE and SAFETY-PROCUREMENT" + unit: none + date: 2026-10-30 + review_by: 2026-12-11 + source: {document: Alex decision 2026-10-30, item: "stop scope; E-stop meaning per E-01-rev1.5; P-03-rev20.1"} + applies_to: + repositories: ["openamrobot-ui", "openamr-platform-sw", "openamr-upperbody-sw", "openamrobot-docs"] + files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.ts", "**/*.tsx", "**/*.js"] + check: + - pattern: '(?P\b(?:software|soft|UI|virtual|on-?screen|panel|app)[ -]E-?stop\b)' + unless: '\bnot\b|never|\bno\b|supersed|historical' + message: E-stop means only the hardwired buttons; the UI control is Stop (software), a functional stop (E-01 rev1.5) + - pattern: '(?P\bStop \(software\)[^\n]{0,60}\b(?:arms?|lift|whole robot|all motion)\b)' + unless: 'target|planned|future|\bwill\b|\bnot\b|base[- ]only' + message: Stop (software) covers base motion only today and is labelled base-only (Alex decision 2026-10-30) + - pattern: '(?P\bStop \(software\)[^\n]{0,40}\bsafety (?:stop|function|rated|evidence)\b)' + unless: '\bnot\b|never|functional' + message: Stop (software) is a functional stop, not a safety function (Alex decision 2026-10-30) + verification: + machine: a software, UI, virtual or panel E-stop; Stop (software) claimed to stop arms or lift today; Stop (software) called a safety stop or function + human: {reviewer: platform-lead, evidence: "UI label showing Stop (software), base-only; platform-sw stop handling covering base motion; the hardwired safety-chain test record for the E-stop buttons"} + owner: platform-lead + + - id: OPERATOR-PANEL + title: Operator panel + summary: "Operator panel: Getac ZX80, 8 inch, 1920 x 1200, landscape." + fix_hint: "Name the Getac ZX80 (8 inch, 1920 x 1200, landscape); label ZX10 material as superseded." + kind: value + status: recorded + value: Getac ZX80, 8 inch, 1920 x 1200, landscape + unit: none + date: 2026-10-28 + review_by: 2026-12-09 + source: {document: P-03-rev20.1, item: "item 24 (whole chest layout, including the ZX80 correction of 28 Oct 2026)"} + supersedes: + - {value: Getac ZX10, source: "earlier panel selection, not held in this register; replaced by P-03-rev20.1 item 24"} + applies_to: + repositories: ["openamrobot-ui", "openamrobot-docs"] + files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.json", "**/*.ts", "**/*.tsx", "**/*.css"] + check: + - pattern: '(?P\bZX10\b)' + unless: 'supersed|replac|historical|earlier|previous|\bwas\b' + message: the operator panel is the Getac ZX80, 8 inch, 1920 x 1200, landscape (P-03 rev20.1 item 24) + - pattern: '(?P\bZX80\b[^\n]{0,60}\bportrait\b|\bportrait\b[^\n]{0,60}\bZX80\b)' + unless: '\bnot\b|never' + message: the ZX80 operator panel is used in landscape (P-03 rev20.1 item 24) + verification: + machine: ZX10 without a superseded label; the ZX80 in portrait + human: {reviewer: software-lead, evidence: UI layout screenshots on the ZX80 at 1920 x 1200 landscape} + owner: platform-lead + + - id: UI-GATE-A + title: UI Gate A date (a UI milestone; not the hardware Gate A, Teensy and MPU6500) + summary: "Full UI Gate A on 6 November 2026; 30 October 2026 was the partial A1 result." + fix_hint: "Give the full UI Gate A as 6 November 2026; call the 30 October result partial (A1)." + kind: value + status: recorded + values: + - full UI Gate A on 6 November 2026 + - 30 October 2026 was the partial A1 result, not the full UI Gate A + - UI Gate A is an openamrobot-ui milestone; it is distinct from the hardware Gate A (Teensy, MPU6500) in BASE-CONTROLLER-GATES + unit: none + date: 2026-10-30 + review_by: 2026-12-11 + source: {document: Alex decision 2026-10-30, item: "UI Gate A date; P-03-rev20.1"} + applies_to: + repositories: ["openamrobot-ui", "openamrobot-docs"] + files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html"] + check: + - pattern: '(?P\bUI Gate A\b[^\n]{0,60}\b30 Oct(?:ober)?\b|\b30 Oct(?:ober)?\b[^\n]{0,60}\bUI Gate A\b)' + unless: 'partial|\bA1\b|supersed|historical|\bwas\b|6 Nov' + message: the full UI Gate A is on 6 November 2026; 30 October 2026 was the partial A1 result (Alex decision 2026-10-30) + verification: + machine: the full UI Gate A dated 30 October without a partial or A1 label + human: {reviewer: software-lead, evidence: UI Gate A test record dated 6 November 2026 and the A1 partial result of 30 October 2026} + owner: platform-lead diff --git a/tests/test_check_decisions.py b/tests/test_check_decisions.py index bb45abe..ab7fd58 100644 --- a/tests/test_check_decisions.py +++ b/tests/test_check_decisions.py @@ -7,6 +7,7 @@ import contextlib import io import os +import re import sys import tempfile import unittest @@ -720,7 +721,7 @@ def test_lift_check_after_cross_repository_dry_run(self): self.assertEqual(self.ids("docs/a.md", text, "x"), [], text) def test_head_camera_lens_42_mm_ahead(self): - # P-03 rev20.0 item 18 (28 Oct 2026): lens 42 mm ahead of the arm mount point, was 22 mm. + # P-03 rev20.1 item 18 (value of the rev20.0 28 Oct 2026 correction): lens 42 mm ahead of the arm mount point, was 22 mm. repo = "openamrobot-docs" for text in ("Head-camera lens 0.022 m ahead of and 0.223 m above the arm mount point.\n", "The ZED Mini lens sits 22 mm ahead of the shoulder.\n"): @@ -732,7 +733,7 @@ def test_head_camera_lens_42_mm_ahead(self): self.assertEqual(self.ids("docs/a.md", text, repo), [], text) def test_head_camera_pitch_baseline_30(self): - # P-03 rev20.0 items 15 and 17 (28 Oct 2026): baseline pitch 30 degrees, hp30; was 25 degrees, hp25. + # P-03 rev20.1 items 15 and 17 (values of the rev20.0 28 Oct 2026 corrections): baseline pitch 30 degrees, hp30; was 25 degrees, hp25. repo = "openamr-upperbody-sw" self.assertEqual(self.ids("docs/a.md", "Head-camera pitch 15 to 35 degrees down, baseline 25 degrees.\n", repo), ["HEAD-CAMERA-IDENTITY"]) @@ -762,6 +763,86 @@ def test_second_estop_is_not_the_openarm_kit_button(self): "Historical: the OpenArm kit chest button.\n"): self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + def test_p03_rev20_1_is_in_force_and_cited(self): + # Owner decision 30 Oct 2026: P-03 rev20.1 is in force; head-camera values stay 30 degrees and hp30. + data = yaml.safe_load((ROOT / "decisions.yaml").read_text(encoding="utf-8")) + self.assertEqual(data["in_force"]["source"], "P-03-rev20.1") + self.assertIn("P-03-rev20.1", data["sources"]) + by_id = {d["id"]: d for d in self.decisions} + for did in ("HEAD-CAMERA-IDENTITY", "CONFIG-IDS", "OPERATOR-PANEL"): + self.assertEqual(by_id[did]["source"]["document"], "P-03-rev20.1", did) + self.assertIn("baseline 30 degrees", " ".join(by_id["HEAD-CAMERA-IDENTITY"]["values"])) + self.assertIn("(baseline hp30)", " ".join(by_id["CONFIG-IDS"]["values"])) + self.assertTrue(by_id["OPERATOR-PANEL"]["source"]["item"].startswith("item 24")) + self.assertIn("ZX80", by_id["OPERATOR-PANEL"]["source"]["item"]) + + def test_ui_manual_velocity_input_is_proposed_and_not_scanned(self): + # Alex decision 30 Oct 2026; source PR openamr-platform-sw #90 open, so stored as open (Proposed). + by_id = {d["id"]: d for d in self.decisions} + entry = by_id["UI-MANUAL-VELOCITY-INPUT"] + self.assertEqual(entry["status"], "open") + self.assertTrue(entry["title"].startswith("Proposed:")) + bad = ("The UI publishes geometry_msgs/Twist on /ui/cmd_vel.\n", + "Call /safety/stop_lock_engage again to release the lock.\n", + "/safety/stop_lock uses best_effort QoS.\n") + good = ("The UI publishes geometry_msgs/TwistStamped on /ui/cmd_vel.\n", + "/safety/stop_lock_engage engages; /safety/stop_lock_release releases.\n", + "/safety/stop_lock is reliable and transient local.\n", + "/ui/cmd_vel is not geometry_msgs/Twist.\n") + for text in bad: + self.assertEqual(self.ids("docs/a.md", text, "openamrobot-ui"), [], text) + self.assertTrue(any(re.search(c["pattern"], text, re.IGNORECASE) + and not re.search(c.get("unless") or "$^", text, re.IGNORECASE) + for c in entry["check"]), text) + for text in good: + self.assertFalse(any(re.search(c["pattern"], text, re.IGNORECASE) + and not re.search(c.get("unless") or "$^", text, re.IGNORECASE) + for c in entry["check"]), text) + + def test_safety_stopped_comes_only_from_the_hardwired_chain(self): + # Alex decision 30 Oct 2026: status vocabulary. + repo = "openamrobot-ui" + for text in ("Set SAFETY_STOPPED when /safety/stop_lock is true.\n", + "A heartbeat timeout raises SAFETY_STOPPED.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), ["STATUS-VOCABULARY"], text) + for text in ("SAFETY_STOPPED comes only from the hardwired chain diagnostic.\n", + "The stop lock shows STOPPED (SOFTWARE), not SAFETY_STOPPED.\n", + "Robot states: READY, EXECUTING, WAITING, BLOCKED, RECOVERING, NEEDS_ASSISTANCE, SAFETY_STOPPED, FAULTED.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + + def test_software_stop_is_base_only_and_not_an_estop(self): + # Alex decision 30 Oct 2026 and E-01 rev1.5: E-stop means only the hardwired buttons. + repo = "openamrobot-ui" + for text in ("Press the software E-stop on the panel.\n", + "The UI E-stop halts the robot.\n", + "Stop (software) halts the base, both arms and the lift.\n", + "Stop (software) is a safety function.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), ["STOP-SCOPE"], text) + for text in ("Stop (software), base-only: stops base motion.\n", + "Target: Stop (software) will cover base, arms and lift.\n", + "Stop (software) is a functional stop, not a safety function.\n", + "There is no software E-stop; E-stop means the hardwired buttons.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + + def test_operator_panel_is_the_getac_zx80(self): + # P-03 rev20.1 item 24: Getac ZX80, 8 inch, 1920 x 1200, landscape; supersedes ZX10. + repo = "openamrobot-ui" + for text in ("Operator panel: Getac ZX10.\n", "Layout for the ZX80 in portrait.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), ["OPERATOR-PANEL"], text) + for text in ("Operator panel: Getac ZX80, 8 inch, 1920 x 1200, landscape.\n", + "The ZX10 was replaced by the ZX80.\n", + "Getac ZX10 (superseded).\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + + def test_ui_gate_a_is_6_november(self): + # Alex decision 30 Oct 2026: full UI Gate A on 6 November 2026; 30 October was partial (A1). + repo = "openamrobot-docs" + self.assertEqual(self.ids("docs/a.md", "UI Gate A passed on 30 October 2026.\n", repo), ["UI-GATE-A"]) + for text in ("Full UI Gate A on 6 November 2026.\n", + "30 October 2026: partial UI Gate A result (A1).\n", + "Gate A (Teensy, MPU6500) test log of 30 October 2026.\n"): + self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + def test_legacy_label_exempts_compute(self): self.assertEqual(self.ids("README.md", "Legacy build: Raspberry Pi 5.\n", "openamr-platform-hw"), []) self.assertEqual(self.ids("README.md", "Compute: Raspberry Pi 5.\n", "openamr-platform-hw"), ["COMPUTE"])