diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed4ec4e..bb0cac5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,12 @@ are in [AGENTS.md](AGENTS.md). - Do not change E-stop, brake, contactor, watchdog, motor-enable or charge-inhibit logic unless the platform lead has agreed it in the issue; such changes need two human reviewers. +**Hardware and procurement.** Every part in a BOM or procurement list must be available from +stock at two or more independent suppliers. A single-source part may appear only as a named +option next to a second-sourced alternative, and a part with no stock is not selected. The rule +is recorded as COMPONENT-AVAILABILITY in [decisions.yaml](decisions.yaml) (P-03 rev20.2 item +26). The automated check reads wording only; the reviewer checks the supplier columns. + ## 4. Open a draft pull request Open the PR as a **draft** and fill in every section of the template: work package, diff --git a/STATE.md b/STATE.md new file mode 100644 index 0000000..1f6b990 --- /dev/null +++ b/STATE.md @@ -0,0 +1,39 @@ +# STATE + +Read this file before any work in this repository and update it in the same PR as the work. +The PR evidence check fails when STATE.md exists and a PR neither updates it nor says +"no change" with a reason. Keep it under 60 lines; facts only, each with a date or SHA. + +## Repository + +- Type and lifecycle: governance, active +- Readiness: not recorded +- Owner role: platform-lead (decisions.yaml); ci-owner (tools/, workflows) +- Verification: `bash rollout/verify.sh`, `python3 tools/check_decisions.py --decisions decisions.yaml --maintainers maintainers.yaml --validate-only`, `python3 tools/check_agent_rules.py --canonical agent-rules/SHARED_RULES.md --file AGENTS.md`; last PASS on main not recorded here (file added 2026-11-01; branch rebased on ed98001) + +## Register in force + +- `in_force`: P-03-rev20.2, 2026-11-01 (change request #77); on main ed98001 (#73 merged) it is P-03-rev20.1, 2026-10-30 +- Canonical BOM: Issue 7.14 (BOM-ISSUE-IN-FORCE), from 2026-11-01 + +## Current work + +| Work package | Branch or PR | Status | Next step | +|---|---|---|---| +| #77 | #78 (claude/optimistic-sagan-oqc2lu) | draft, rebased on ed98001 | owner review of the P-03 rev20.2 item 26 values and the OPERATOR-PANEL portrait change | +| P-03 rev20.1 | #73 | merged as ed98001 (2026-11-01) | none; its OPERATOR-PANEL portrait check is narrowed in #78 | +| PLATFORM-PIN | #74 | open, not merged (2026-11-01) | touches decisions.yaml; rebase after whichever merges first | + +## Decisions this repository implements + +This repository holds the register (decisions.yaml) and its checkers; it implements no robot values. + +## Known gaps + +- Safety-path register updates (precharge fitted, K1/K2 decision 2 Nov 2026) left to the electrical owner; SAFETY-PROCUREMENT and SAFETY-CHAIN-IO unchanged since 71c439e +- "Chest E-stop" wording retirement (P-03 rev20.2 item 26) not recorded; it belongs to SAFETY-PROCUREMENT + +## Not verified + +- Register values against the P-03 rev20.2 source document (held outside any repository), since 2026-11-01 +- Scans of consumer repositories against the new patterns, since 2026-11-01 diff --git a/WATCHDOG.md b/WATCHDOG.md index 839100d..6deae9c 100644 --- a/WATCHDOG.md +++ b/WATCHDOG.md @@ -121,19 +121,19 @@ identical to the register. | Decision | Status | Accepted on the same line (any one; case does not matter) | |---|---|---| -| BOM-ISSUE-IN-FORCE | recorded | *BOM Issue 7.10 is the canonical hardware BOM*: `supersed (superseded, supersedes)`, `replaced`, `previous`, `earlier`
*B-01 is superseded*: `supersed (superseded, supersedes)`
*BOM Issue 7.10 is the canonical hardware BOM*: `supersed (superseded, supersedes)`, `replaced`, `previous`, `earlier`, `historical`
*superseded source still cited (P-03-rev18.1 line 7); cite P-03-rev19.6*: no label; correct the line or add a decision-allow marker | +| BOM-ISSUE-IN-FORCE | recorded | *BOM Issue 7.14 is the canonical hardware BOM*: `supersed (superseded, supersedes)`, `replaced`, `previous`, `earlier`
*B-01 is superseded*: `supersed (superseded, supersedes)`
*BOM Issue 7.14 is the canonical hardware BOM; Issue 7 to 7.13 (including 7.10 and 7.11) are retired*: `supersed (superseded, supersedes)`, `replaced`, `previous`, `earlier`, `historical`
*superseded source still cited (P-03-rev18.1 line 7); cite P-03-rev20.2*: no label; correct the line or add a decision-allow marker | | MAST-INSTALL-HEIGHT | superseded | `supersed (superseded, supersedes)`, `legacy`, `historical`, `earlier`, `previous`, `rev 18.1 to rev 18.6`, `lift`, `L1350`, `extended`, `full stroke` | | MAST-POSITIONS | superseded | `supersed (superseded, supersedes)`, `legacy`, `historical`, `earlier`, `previous`, `rev 18.1 to rev 18.6` | | MAST-TOP-HEIGHT | superseded | `supersed (superseded, supersedes)`, `legacy`, `historical`, `earlier`, `previous`, `rev 18.1 to rev 18.6` | | MAX-ASSEMBLED-HEIGHT | recorded | *maximum assembled height is 1700 mm*: no label; correct the line or add a decision-allow marker
*1700 mm is the assembled-height envelope, not a shoulder height or mast position*: `max`, `envelope`, `not shoulder, not the shoulder`, `higher`, `supersed (superseded, supersedes)` | | DATUM-HEIGHT-STACK | recorded | *the steel chassis deck top is 294 mm above the floor*: `supersed (superseded, supersedes)`, `historical`, `legacy`, `earlier`, `previous`
*the lift base-plate top face is 304 mm above the floor, the reference for lift and shoulder heights*: `supersed (superseded, supersedes)`, `historical`, `legacy`, `earlier`, `previous` | -| FRAMES-REP105 | recorded | *base_link is at the drive-axle midpoint and axle height; base_footprint is on the floor*: `base_footprint`, `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy`
*base_footprint is on the floor under the drive-axle midpoint*: `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy`
*imu_link sits on the centreline away from motor magnetic fields*: `away`, `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy` | +| FRAMES-REP105 | recorded | *base_link is at the drive-axle midpoint and axle height; base_footprint is on the floor*: `base_footprint`, `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy`
*base_footprint is on the floor under the drive-axle midpoint*: `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy`
*imu_link sits on the centreline away from motor magnetic fields*: `away`, `not`, `never`, `supersed (superseded, supersedes)`, `historical`, `legacy`
*the imu_link board is the TDK EV_ICM-42688-P; MIKROE-4237 is superseded*: `supersed (superseded, supersedes)`, `replac`, `historical`, `legacy`, `earlier`, `previous`, `no longer`, `was`, `instead of` | | NEAR-FIELD-FRAMES | recorded | *the ToF cliff pitch is 10 degrees down, not 20*: `supersed (superseded, supersedes)`, `replac`, `previous`, `earlier`, `historical`, `instead of`, `was 20`, `from 20`
*the 10 degree ToF pitch is confirmed only by the bench test*: `bench`, `until` | | BATTERY-PLACEMENT | recorded | `supersed (superseded, supersedes)`, `historical`, `previous`, `earlier`, `rev 18.1 to rev 18.6` | | SPEED-CEILING | recorded | *1.5 m/s is a command ceiling, not an operating limit*: `ceiling`, `analytical`
*1.5 m/s is not an accepted operating speed*: `ceiling`, `analytical`, `not` | -| DRIVETRAIN | recorded | *the selected motor variant is ZLLG80ASM250-L-B*: no label; correct the line or add a decision-allow marker
*ZLTECH is the selected 2.0 drivetrain, not an option*: no label; correct the line or add a decision-allow marker
*brake fail-safe function and ratings are open supplier-evidence gates*: `pending`, `F2A`, `verif`, `not yet`, `unverified` | +| DRIVETRAIN | recorded | *the selected motor variant is ZLLG80ASM250-L-B*: no label; correct the line or add a decision-allow marker
*ZLTECH is the selected 2.0 drivetrain, not an option*: no label; correct the line or add a decision-allow marker
*brake ratings are recorded (ZLTECH 18 Oct 2026); the fail-safe behaviour still needs verification evidence*: `pending`, `F2A`, `verif`, `not yet`, `unverified` | | RS485-NOT-IN-2-0 | recorded | *RS485 is not provisioned in 2.0*: `not used`, `no RS485`, `not provisioned`, `without`, `legacy`, `Gate A`, `removed`, `not in 2.0`
*CAN1 and CAN2 are dedicated; no upper-body serial link to the base*: no label; correct the line or add a decision-allow marker | -| BASE-CONTROLLER-GATES | recorded | *Teensy is the Gate A controller and the release fallback, not a bench target*: no label; correct the line or add a decision-allow marker
*the Gate A IMU is the MPU6500*: `would return`, `labeled`, `labelled`, `assumed`, `not a real`, `instead of`, `USE_MPU6050`, `driver` | +| BASE-CONTROLLER-GATES | recorded | *Teensy is the Gate A controller and the release fallback, not a bench target*: no label; correct the line or add a decision-allow marker
*the Gate A IMU is the MPU6500*: `would return`, `labeled`, `labelled`, `assumed`, `not a real`, `instead of`, `USE_MPU6050`, `driver`
*the 2.0 IMU board is the TDK EV_ICM-42688-P on SPI4; MIKROE-4237 is superseded*: `supersed (superseded, supersedes)`, `replac`, `historical`, `legacy`, `earlier`, `previous`, `no longer`, `was`, `instead of` | | BASE-CONTROLLER-IO | recorded | *the base controller is the STM32H723ZG on the NUCLEO-H723ZG; label STM32H743 / NUCLEO-H743ZI2 material as superseded*: `supersed (superseded, supersedes)`, `legacy`, `historical`, `replaced`
*the ultrasonic sensors are four MaxBotix MB7060 on dedicated STM32 UARTs at 9600 8N1; no sensor I2C*: `supersed (superseded, supersedes)`, `legacy`, `historical`, `replaced`
*micro-ROS runs over UDP on Ethernet between MCU and Jetson; USB is for the bench only*: `bench`, `legacy`, `Gate A`, `Teensy`, `supersed (superseded, supersedes)`, `historical`
*the base has four MaxBotix MB7060 (edge holes) and two TF-Luna ToF cliff sensors (centre holes)*: `supersed (superseded, supersedes)`, `legacy`, `historical` | | IMU-TOPIC-OWNERSHIP | recorded | *firmware publishes /imu/data_raw; /imu/data belongs to the host filter*: `host`, `filter (not unfiltered)`, `EKF`, `madgwick`, `must not`, `never`, `outdated`, `older revision(s)`
*firmware must not publish filtered /imu/data*: no label; correct the line or add a decision-allow marker | | CAMERAS | recorded | *the base camera is the Orbbec Gemini 336L*: `legacy`, `historical`, `previous`, `not used`, `replaced`
*the base camera tilts about 10 degrees up*: no label; correct the line or add a decision-allow marker
*the 2.0 base camera is the Orbbec Gemini 336L*: `legacy`, `Gate A`, `historical`
*the Gemini 336L up-tilt positions are 5, 10 and 15 degrees, baseline 10*: `supersed (superseded, supersedes)`, `historical`, `legacy`
*the base camera is not at 243 mm; base_camera_link is (0.387, 0, 0.046) from base_link*: `supersed (superseded, supersedes)`, `historical` | @@ -163,8 +163,10 @@ identical to the register. | 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` | +| OPERATOR-PANEL | recorded | *the operator panel is the Getac ZX80, 8 inch, 1920 x 1200*: `supersed (superseded, supersedes)`, `replac`, `historical`, `earlier`, `previous`, `was`
*the ZX80 is landscape by default; portrait is the second locked position*: `supersed (superseded, supersedes)`, `historical`, `not`, `was`, `landscape \(?(?:by )?default` | | UI-GATE-A | recorded | `partial`, `A1`, `supersed (superseded, supersedes)`, `historical`, `was`, `6 Nov` | +| COMPONENT-AVAILABILITY | recorded | `option`, `alternative`, `second[- ]source`, `not`, `never`, `supersed (superseded, supersedes)`, `historical` | +| TABLET-MOUNT | recorded | *the tablet mount default is landscape; portrait is the second locked position*: `supersed (superseded, supersedes)`, `historical`, `not`, `was`, `landscape \(?(?:by )?default`
*the tablet position is a mount setting, not a configuration ID*: `not`, `never`, `supersed (superseded, supersedes)`, `historical` | ## Approved public contacts diff --git a/decisions.yaml b/decisions.yaml index 61d5c1c..40d53a2 100644 --- a/decisions.yaml +++ b/decisions.yaml @@ -53,12 +53,18 @@ schema_version: 1 in_force: - source: P-03-rev20.1 - date: 2026-10-30 + source: P-03-rev20.2 + date: 2026-11-01 sources: + P-03-rev20.2: + title: "OpenAMRobot 2.0 Decision Addendum rev20.2, 1 November 2026 (the addendum in force; item 26: BOM Issue 7.14, component availability, tablet mount, SAFE 24 V converter, EV_ICM-42688-P IMU board, hub-motor brake data, DOLD holding confirmation)" + evidence: plan-set document, not held in any repository; provenance only, CI never reads it; values entered from the owner's summary of item 26 (change request openAMRobot/.github#77) + BOM-Issue-7.14: + title: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.14, 27 October 2026, with the 1 November 2026 changes (canonical per P-03 rev20.2) + evidence: not held in any repository; provenance only, CI never reads it 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)" + title: "OpenAMRobot 2.0 Decision Addendum rev20.1, with the platform lead's decisions of 30 October 2026 (earlier revision; superseded by rev20.2 as 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 @@ -92,7 +98,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 (earlier revision; superseded by rev20.1 as 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; 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) @@ -134,7 +140,7 @@ sources: title: P-00 Master Coordination Plan, revision 18.1 evidence: plan-set document, not held in any repository; cited by text line BOM-Issue-7.10: - title: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.10, 18 October 2026 (canonical per P-03 rev19.6) + title: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.10, 18 October 2026 (earlier issue; canonical per P-03 rev19.6) evidence: not held in any repository; provenance only, CI never reads it BOM-Issue-7.8: title: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.8, 15 October 2026 (earlier issue; canonical per P-03 rev19.2) @@ -170,16 +176,18 @@ exclude: decisions: - id: BOM-ISSUE-IN-FORCE title: Canonical hardware BOM issue for OpenAMRobot 2.0 - summary: "BOM Issue 7.10 is the canonical OpenAMRobot 2.0 hardware BOM." - fix_hint: "Name Issue 7.10 as canonical, or label older issues (Issue 6, Issue 7 to 7.9, B-01) as superseded or earlier." + summary: "BOM Issue 7.14 is the canonical OpenAMRobot 2.0 hardware BOM." + fix_hint: "Name Issue 7.14 as canonical, or label older issues (Issue 6, Issue 7 to 7.13, B-01) as superseded or earlier." kind: value status: recorded - value: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.10 + value: OpenAMRobot 2.0 Detailed Hardware BOM, Issue 7.14 (27 October 2026, with the 1 November 2026 changes) unit: none - date: 2026-10-18 - review_by: 2026-11-29 - source: {document: P-03-rev19.6, item: "BOM issue in force (BOM Issue 7.10)"} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (BOM Issue 7.14)"} supersedes: + - {value: Issue 7.11, source: "earlier issue; replaced by Issue 7.14 per P-03-rev20.2 item 26"} + - {value: Issue 7.10, source: "P-03-rev19.6 BOM issue in force (BOM Issue 7.10)"} - {value: Issue 7.8, source: "P-03-rev19.2 BOM issue in force (BOM Issue 7.8)"} - {value: Issue 7.7, source: "P-03-rev19.0 BOM issue in force (BOM Issue 7.7)"} - {value: Issue 7.6, source: "P-03-rev18.9 item 16 (BOM Issue 7.6 carries the change)"} @@ -193,15 +201,15 @@ decisions: check: - pattern: '(?P(?:canonical|in force|of record)[^\n]{0,40}\bIssue\s*6\b|\bIssue\s*6\b[^\n]{0,40}\b(?:canonical|in force|of record))' unless: 'supersed|replaced|previous|earlier' - message: BOM Issue 7.10 is the canonical hardware BOM (P-03 rev19.6) + message: BOM Issue 7.14 is the canonical hardware BOM (P-03 rev20.2 item 26) - pattern: '(?PB-01[^\n]{0,60}\b(?:canonical|current))' unless: 'supersed' message: B-01 is superseded - - pattern: '(?P(?:canonical|in force|of record)[^\n]{0,40}\bIssue\s*7(?!\.10\b)(?:\.\d+)?\b|\bIssue\s*7(?!\.10\b)(?:\.\d+)?\b[^\n]{0,40}\b(?:canonical|in force|of record))' + - pattern: '(?P(?:canonical|in force|of record)[^\n]{0,40}\bIssue\s*7(?!\.14\b)(?:\.\d+)?\b|\bIssue\s*7(?!\.14\b)(?:\.\d+)?\b[^\n]{0,40}\b(?:canonical|in force|of record))' unless: 'supersed|replaced|previous|earlier|historical' - message: BOM Issue 7.10 is the canonical hardware BOM (P-03 rev19.6) + message: BOM Issue 7.14 is the canonical hardware BOM; Issue 7 to 7.13 (including 7.10 and 7.11) are retired (P-03 rev20.2 item 26) verification: - machine: Issue 6, Issue 7 to 7.9 or B-01 presented as the canonical BOM; citations of P-03 rev18.1 line 7 + machine: Issue 6, Issue 7 to 7.13 (including 7.10 and 7.11) or B-01 presented as the canonical BOM; citations of P-03 rev18.1 line 7 human: {reviewer: platform-lead, evidence: the BOM file header and issue number in the released BOM} owner: platform-lead @@ -358,13 +366,14 @@ decisions: - base_link at the drive-axle midpoint and axle height; x forward, z up - base_link 0.098 m above the floor (axle height; moved here from CAMERAS on 20 Oct 2026, value unchanged, P-03 rev19.4 item 19 D3) - imu_link on the centreline, away from motor magnetic fields - - "imu_link (0.0544, 0, 0.1210) m from base_link, rpy (0, 0, -pi/2), yaw -90 deg: ICM-42688-P on MIKROE-4237 on the electronics bracket MMP.04.00.00.002, centreline, 54.4 mm ahead of the drive axle, chip centre 165.043 mm above the base-plate top, 219.04 mm above the floor" + - "imu_link (0.0544, 0, 0.1210) m from base_link, rpy (0, 0, -pi/2), yaw -90 deg (unchanged): ICM-42688-P on the TDK EV_ICM-42688-P board (replaces MIKROE-4237, P-03 rev20.2 item 26), chip mounted +Y forward, on the electronics bracket MMP.04.00.00.002, centreline, 54.4 mm ahead of the drive axle, chip centre 165.043 mm above the base-plate top, 219.04 mm above the floor" - "the IMU publishes raw sensor-frame values; the axis mapping is in the URDF (sensor X to base -y, Y to +x, Z to +z); no firmware remap" unit: none - date: 2026-10-16 - review_by: 2026-11-27 - source: {document: P-03-rev19.4, item: "item 19 D5 update (Alex decision 16 Oct 2026, platform-sw #70)"} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (IMU board TDK EV_ICM-42688-P, chip +Y forward; pose unchanged); imu_link pose per P-03-rev19.4 item 19 D5 update (Alex decision 16 Oct 2026, platform-sw #70)"} supersedes: + - {value: "imu_link (0.0544, 0, 0.1210), yaw -90 deg: ICM-42688-P on MIKROE-4237", source: "P-03-rev19.4 item 19 D5 update; board replaced by P-03-rev20.2 item 26"} - {value: "(0.0544, 0, 0.133), rpy 0", source: "measured against a displaced base plate in the CAD; superseded by Alex decision 16 Oct 2026 (platform-sw #70)"} - {value: "(0.195, 0, -0.044) plus board stack", source: "Alex decision 12 Oct 2026, superseded by Alex decision 16 Oct 2026 (CAD Rev B, platform-hw #20)"} applies_to: {repositories: ["*"], files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.xacro", "**/*.urdf", "**/*.launch.py"]} @@ -378,8 +387,13 @@ decisions: - pattern: '(?P\bimu_link\b[^\n]{0,40}\b(?:next to|beside|on|near|above) the (?:drive |hub |wheel )?motors?\b)' unless: '\baway\b|\bnot\b|never|supersed|historical|legacy' message: imu_link sits on the centreline away from motor magnetic fields (P-03 rev18.7 item 15) + # Robot-description files only; BASE-CONTROLLER-GATES carries the same check for text files. + - pattern: '(?P\bMIKROE-?4237\b)' + files: ["**/*.xacro", "**/*.urdf", "**/*.launch.py"] + unless: 'supersed|replac|historical|legacy|earlier|previous|no longer|\bwas\b|instead of' + message: the imu_link board is the TDK EV_ICM-42688-P; MIKROE-4237 is superseded (P-03 rev20.2 item 26) verification: - machine: base_link placed on the floor, base_footprint at axle height, or imu_link next to a motor + machine: base_link placed on the floor, base_footprint at axle height, imu_link next to a motor, or MIKROE-4237 presented as current in a robot-description file human: {reviewer: robot-description-owner, evidence: URDF frame tree and real TF on the robot matching REP 105} owner: platform-lead @@ -468,14 +482,18 @@ decisions: - id: DRIVETRAIN title: Drivetrain summary: "Two ZLTECH ZLLG80ASM250-L-B hub motors with brakes, one ZLAC8015D driver on CAN1." - fix_hint: "Name the ZLLG80ASM250-L-B as selected; mark brake ratings as pending supplier evidence." + fix_hint: "Name the ZLLG80ASM250-L-B; brakes 24 V, 12.5 W, 6 Nm holding (ZLTECH 18 Oct 2026); fail-safe needs evidence." kind: value status: recorded - value: Two ZLTECH ZLLG80ASM250-L-B hub motors with brakes, one ZLAC8015D V4.2 driver on CAN1 (CANopen), 200 mm wheels + values: + - Two ZLTECH ZLLG80ASM250-L-B hub motors with brakes, one ZLAC8015D V4.2 driver on CAN1 (CANopen), 200 mm wheels + - "hub-motor brakes (ZLTECH 18 Oct 2026): 24 V, 12.5 W per motor, 6 Nm holding, release at most 100 ms, engagement at most 30 ms without diode" unit: none - date: 2026-09-21 - review_by: 2026-11-02 - source: {document: P-00-rev18.1, item: txt 16} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (hub-motor brake data, ZLTECH 18 Oct 2026); motors and driver per P-00-rev18.1 txt 16"} + supersedes: + - {value: "brake ratings pending supplier evidence", source: "P-00-rev18.1 txt 16 (register fix_hint until P-03-rev20.2 item 26)"} applies_to: repositories: ["openamr-platform-*", "openamr-upperbody-*", "openamrobot-docs", "openamrobot-release", ".github"] files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html", "**/*.xacro", "**/*.urdf"] @@ -486,10 +504,10 @@ decisions: message: ZLTECH is the selected 2.0 drivetrain, not an option - pattern: '(?Pfail-safe brakes?)' unless: 'pending|F2A|verif|not yet|unverified' - message: brake fail-safe function and ratings are open supplier-evidence gates + message: brake ratings are recorded (ZLTECH 18 Oct 2026); the fail-safe behaviour still needs verification evidence verification: machine: the unbraked motor variant, ZLTECH described as optional, brakes described as fail-safe - human: {reviewer: platform-lead, evidence: supplier datasheets for the brake rating and fail-safe behaviour} + human: {reviewer: platform-lead, evidence: ZLTECH brake data of 18 Oct 2026 and supplier evidence of the fail-safe behaviour} owner: platform-lead - id: RS485-NOT-IN-2-0 @@ -519,20 +537,22 @@ decisions: - id: BASE-CONTROLLER-GATES title: Base-controller gates and the 2.0 IMU - summary: "Gate A (Teensy, MPU6500) and Gate B (STM32H723ZG) stay distinct; the ICM-42688-P is the 2.0 IMU." - fix_hint: "Call the Teensy and MPU6500 Gate A (legacy); the 2.0 IMU is the ICM-42688-P (MIKROE-4237, SPI)." + summary: "Gate A (Teensy, MPU6500) and Gate B (STM32H723ZG) stay distinct; the 2.0 IMU is the ICM-42688-P on EV_ICM-42688-P." + fix_hint: "Call Teensy and MPU6500 Gate A (legacy); 2.0 IMU board is TDK EV_ICM-42688-P on SPI4; MIKROE-4237 is superseded." kind: distinction status: recorded values: - Gate A, Jetson with the existing Teensy, ZBLD/PWM drivetrain and MPU6500 (legacy test configuration) - Gate B, Jetson with the STM32H723ZG (NUCLEO-H723ZG bench board) and the ZLTECH drivers - - the ICM-42688-P (MIKROE-4237, SPI) is the OpenAMRobot 2.0 IMU, WHO_AM_I 0x47 at boot; the MPU6500 stays the Gate A (legacy) IMU + - the ICM-42688-P on the TDK EV_ICM-42688-P board (SPI4, Mode 0) is the OpenAMRobot 2.0 IMU, WHO_AM_I 0x47 at boot; it replaces the MIKROE-4237 board (same sensor); the MPU6500 stays the Gate A (legacy) IMU + - no click shield on the NUCLEO-H723ZG; the IMU board and the CAN isolator are wired directly to SPI4 and FDCAN2 - STM32 go/no-go 6 November 2026; fallback is the validated legacy Teensy/PWM build behind I8 unit: none - date: 2026-10-12 - review_by: 2026-11-23 - source: {document: P-03-rev19.0, item: "item 19 (base-controller IMU)"} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (IMU board TDK EV_ICM-42688-P, no click shield); Gate A/B distinction and IMU per P-03-rev19.0 item 19 (base-controller IMU)"} supersedes: + - {value: "the ICM-42688-P (MIKROE-4237, SPI) is the OpenAMRobot 2.0 IMU", source: "P-03-rev19.0 item 19 (base-controller IMU); board replaced by P-03-rev20.2 item 26"} - {value: "ICM-42688-P enters the manufacturing BOM only after side-by-side robot evidence; adoption decision 20 November 2026", source: "P-00-rev18.1 txt 63, 101, 106, 344; I8-WP adoption gate"} applies_to: {repositories: ["*"], files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html"]} check: @@ -541,9 +561,12 @@ decisions: - pattern: '\b(?PMPU6050)\b' unless: 'would return|labeled|labelled|assumed|not a real|instead of|USE_MPU6050|driver' message: the Gate A IMU is the MPU6500 + - pattern: '(?P\bMIKROE-?4237\b)' + unless: 'supersed|replac|historical|legacy|earlier|previous|no longer|\bwas\b|instead of' + message: the 2.0 IMU board is the TDK EV_ICM-42688-P on SPI4; MIKROE-4237 is superseded (P-03 rev20.2 item 26) verification: - machine: Teensy called a bench target, MPU6050 named as the IMU - human: {reviewer: platform-lead, evidence: "Gate A test log, STM32 go/no-go record, ICM-42688-P WHO_AM_I 0x47 boot log"} + machine: Teensy called a bench target, MPU6050 named as the IMU, MIKROE-4237 presented as the current IMU board + human: {reviewer: platform-lead, evidence: "Gate A test log, STM32 go/no-go record, ICM-42688-P WHO_AM_I 0x47 boot log on the EV_ICM-42688-P over SPI4"} owner: platform-lead - id: BASE-CONTROLLER-IO @@ -701,11 +724,12 @@ decisions: - main battery bus 25.6 V nominal (not a regulated 24 V rail) - regulated 24 V branch (safety relay, peripherals); the brake-release supply comes from the actuator side, see ESTOP-SCOPE - regulated 5 V (includes the navigation LiDAR) + - "SAFE 24 V converter for lift and brake release: MEAN WELL DDR-240B-24 (16.8 to 33.6 V in, 24 V 10 A, 240 W) behind F-LIFT 15 A, 2.5 mm2 (P-03 rev20.2 item 26)" - no 12 V rail unit: V date: null - review_by: 2026-11-18 # six weeks after this register update (2026-10-07) - source: {document: P-03-rev18.1, item: "item 2 line 10; P-03-rev18.4 item 13; brake-release supply per P-03-rev19.2 item 20"} + review_by: 2026-12-13 # six weeks after this register update (2026-11-01) + source: {document: P-03-rev18.1, item: "item 2 line 10; P-03-rev18.4 item 13; brake-release supply per P-03-rev19.2 item 20; SAFE 24 V converter per P-03-rev20.2 item 26"} supersedes: - {value: "regulated 24 V branch (safety relay, brake coils, peripherals)", source: "P-03-rev18.4 item 13"} applies_to: @@ -720,7 +744,7 @@ decisions: message: there is no 12 V rail in 2.0 verification: machine: a 12 V rail or a 24 V to 5/12 V converter in text - human: {reviewer: platform-lead, evidence: power distribution schematic} + human: {reviewer: platform-lead, evidence: "power distribution schematic, including the DDR-240B-24 SAFE 24 V branch behind F-LIFT"} owner: platform-lead - id: DOCK-NO-CONTACTS @@ -1199,12 +1223,14 @@ decisions: - lift base plate fore-aft positions centre and +50 mm (bp000, bp050) - lift motion only in the stowed or carry safe pose with the base stopped - hold-and-move limits 0.05 m/s (drawer reversal) and 0.3 m/s (carrying) - - "release gates: written DOLD holding on E-stop and power loss, supplier CAD, weighed mass, the CAN3 lift interface and the F2S rerun" + - "DOLD written confirmation received 14 Oct 2026: 800 N held with power off" + - "release gates: the holding test on the delivered unit after an E-stop during motion and after power loss, supplier CAD, weighed mass, the CAN3 lift interface and the F2S rerun" unit: none - date: 2026-10-09 - review_by: 2026-11-20 - source: {document: P-03-rev18.9, item: "items 16 and 18; motion rules item 15; release gates items 6, 8, 14, 15, 16"} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (DOLD confirmation of 14 Oct 2026; holding-test release gate); lift, motion rules and other release gates per P-03-rev18.9 items 6, 8, 14, 15, 16 and 18"} supersedes: + - {value: "release gate: written DOLD holding on E-stop and power loss", source: "P-03-rev18.9 items 6, 8, 14, 15, 16"} - {value: "Lift removed from OpenAMRobot 2.0; fixed mast; lift on the 3.0 roadmap (former entry LIFT-REMOVED)", source: P-00-rev18.1 txt 18} - {value: "DOLD Hexalift V1 350 mm primary", source: P-03-rev18.7 item 15} - {value: "base plate positions centre, +50, +100, +150 and +200 mm", source: P-03-rev18.7 item 15} @@ -1227,7 +1253,7 @@ decisions: message: the lift base plate has two fore-aft positions, centre and +50 mm (P-03 rev18.9 item 18) verification: machine: text saying 2.0 has no lift, presenting a fixed mast as current, deferring the lift to 3.0, naming Hexalift V1 as current, or base-plate positions beyond +50 mm - human: {reviewer: platform-lead, evidence: "P-03 rev18.9 items 16 and 18, the option B drawing set and the release-gate records: DOLD holding on E-stop and power loss, supplier CAD, weighed mass, CAN3 lift interface, F2S rerun"} + human: {reviewer: platform-lead, evidence: "P-03 rev18.9 items 16 and 18, the option B drawing set and the release-gate records: DOLD confirmation of 14 Oct 2026, the holding-test record on the delivered unit after an E-stop during motion and after power loss, supplier CAD, weighed mass, CAN3 lift interface, F2S rerun"} owner: platform-lead - id: ARM-MOUNT @@ -1280,6 +1306,7 @@ decisions: date: 2026-10-28 review_by: 2026-12-09 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"} + note: The tablet position (landscape or portrait, TABLET-MOUNT) is a mount setting, not a configuration ID (P-03 rev20.2 item 26). 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} @@ -1431,16 +1458,17 @@ decisions: - 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." + summary: "Operator panel: Getac ZX80, 8 inch, 1920 x 1200; rotatable mount, landscape by default, portrait second (TABLET-MOUNT)." + fix_hint: "Name the Getac ZX80 (8 inch, 1920 x 1200), landscape by default, portrait second; label ZX10 as superseded." kind: value status: recorded - value: Getac ZX80, 8 inch, 1920 x 1200, landscape + value: Getac ZX80, 8 inch, 1920 x 1200, rotatable mount, landscape by default with a portrait second position (see TABLET-MOUNT, P-03 rev20.2 item 26) 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)"} + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.1, item: "item 24 (whole chest layout, including the ZX80 correction of 28 Oct 2026); mount positions per P-03-rev20.2 item 26 (TABLET-MOUNT)"} supersedes: + - {value: "Getac ZX80, 8 inch, 1920 x 1200, landscape (no portrait position)", source: "P-03-rev20.1 item 24; portrait second position added by P-03-rev20.2 item 26"} - {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"] @@ -1448,13 +1476,16 @@ decisions: 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) + message: the operator panel is the Getac ZX80, 8 inch, 1920 x 1200 (P-03 rev20.1 item 24) + # Portrait is the approved second locked position (P-03 rev20.2 item 26); only portrait as the + # default is a finding. UI code files only: TABLET-MOUNT carries the same check for text files. + - pattern: '(?P\b(?:ZX80|tablet)\b[^\n]{0,60}\bportrait\b[^\n]{0,20}\b(?:by|as (?:the)?|is (?:the)?) ?default\b|\b(?:ZX80|tablet)\b[^\n]{0,60}\bdefault\b[^\n]{0,20}\bportrait\b|\bportrait\b[^\n]{0,20}\b(?:is |as )?(?:the )?default\b[^\n]{0,60}\b(?:ZX80|tablet)\b)' + files: ["**/*.json", "**/*.ts", "**/*.tsx", "**/*.css"] + unless: 'supersed|historical|\bnot\b|\bwas\b|landscape \(?(?:by )?default' + message: the ZX80 is landscape by default; portrait is the second locked position (P-03 rev20.2 item 26, TABLET-MOUNT) 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} + machine: ZX10 without a superseded label; portrait presented as the ZX80 default in UI code + human: {reviewer: software-lead, evidence: "UI layout screenshots on the ZX80 at 1920 x 1200 in landscape (default) and portrait"} owner: platform-lead - id: UI-GATE-A @@ -1482,3 +1513,60 @@ decisions: 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 + + - id: COMPONENT-AVAILABILITY + title: Component availability rule for BOM and procurement + summary: "Every part in stock at 2+ independent suppliers; single-source only as an option; no-stock parts not selected." + fix_hint: "Give a second-sourced alternative next to a single-source part, or list it as a named option, not the selection." + kind: limit + status: recorded + values: + - every part is available from stock at two or more independent suppliers + - a single-source part appears only as a named option next to a second-sourced alternative + - parts with no stock are not selected + unit: none + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (component availability rule)"} + applies_to: + repositories: ["*"] + files: ["**/*BOM*.md", "**/*bom*.md", "**/*BOM*.yaml", "**/*bom*.yaml", "**/*BOM*.yml", "**/*bom*.yml", + "**/procurement/**/*.md", "**/procurement/**/*.yaml", "**/procurement/**/*.yml", + "**/*procurement*.md", "**/*procurement*.yaml", "**/*procurement*.yml"] + check: + # Wording only: the scan cannot see supplier stock or count suppliers. + - pattern: '(?P\bsingle[- ]source[ds]?\b[^\n]{0,40}\b(?:selected|chosen|baseline|primary)\b|\b(?:selected|chosen|baseline|primary)\b[^\n]{0,40}\bsingle[- ]source[ds]?\b)' + unless: 'option|alternative|second[- ]source|\bnot\b|never|supersed|historical' + message: a single-source part appears only as a named option next to a second-sourced alternative (P-03 rev20.2 item 26) + verification: + machine: "wording only: a single-source part presented as selected, chosen, baseline or primary without an option or alternative label; supplier stock and supplier count are not checked" + human: {reviewer: platform-lead, evidence: "BOM Issue 7.14 supplier columns showing stock at two or more independent suppliers per line"} + owner: platform-lead + + - id: TABLET-MOUNT + title: Operator tablet on a rotatable mount with two locked positions + summary: "Getac ZX80 on a rotatable mount, two locked positions: landscape (default, 234 x 149.8 mm) and portrait." + fix_hint: "Describe landscape as the default position and portrait as the second locked position; it is not a configuration ID." + kind: configuration + status: recorded + values: + - Getac ZX80 tablet on a rotatable mount with two locked positions + - landscape, default, 234 x 149.8 mm + - portrait, 149.8 x 234 mm + - the position is a mount setting, not a configuration ID (see CONFIG-IDS) + unit: mm + date: 2026-11-01 + review_by: 2026-12-13 + source: {document: P-03-rev20.2, item: "item 26 (tablet mount)"} + applies_to: {repositories: ["*"], files: ["**/*.md", "**/*.yaml", "**/*.yml", "**/*.html"]} + check: + - pattern: '(?P\b(?:ZX80|tablet)\b[^\n]{0,60}\bportrait\b[^\n]{0,20}\b(?:by|as (?:the)?|is (?:the)?) ?default\b|\b(?:ZX80|tablet)\b[^\n]{0,60}\bdefault\b[^\n]{0,20}\bportrait\b|\bportrait\b[^\n]{0,20}\b(?:is |as )?(?:the )?default\b[^\n]{0,60}\b(?:ZX80|tablet)\b)' + unless: 'supersed|historical|\bnot\b|\bwas\b|landscape \(?(?:by )?default' + message: the tablet mount default is landscape; portrait is the second locked position (P-03 rev20.2 item 26) + - pattern: '(?P\btablet[ _](?:position|orientation)\b[^\n]{0,40}\bconfiguration[ _]ID\b)' + unless: '\bnot\b|never|supersed|historical' + message: the tablet position is a mount setting, not a configuration ID (P-03 rev20.2 item 26) + verification: + machine: portrait presented as the default tablet position; the tablet position presented as a configuration ID + human: {reviewer: platform-lead, evidence: "tablet mount drawing with the two locked positions and the ZX80 outline dimensions"} + owner: platform-lead diff --git a/tests/test_check_decisions.py b/tests/test_check_decisions.py index ab7fd58..d60d1e6 100644 --- a/tests/test_check_decisions.py +++ b/tests/test_check_decisions.py @@ -427,13 +427,20 @@ def test_bom_issue_in_force(self): self.assertEqual(self.ids("docs/a.md", "BOM per P-03 rev18.1 line 7.\n", "x"), ["BOM-ISSUE-IN-FORCE"]) self.assertEqual(self.ids("docs/a.md", "Issue 7.6 is canonical; Issue 6 is superseded.\n", "x"), []) - def test_bom_issue_7_10_in_force(self): + # P-03 rev20.2 item 26: BOM Issue 7.14 is canonical; Issue 7.10 and 7.11 are retired. + def test_bom_issue_7_14_in_force(self): for text in ("The canonical BOM is Issue 7.\n", "BOM Issue 7.1 is the BOM of record.\n", - "BOM Issue 7.8 is canonical.\n", "BOM Issue 7.9 is canonical.\n"): + "BOM Issue 7.8 is canonical.\n", "BOM Issue 7.9 is canonical.\n", + "BOM Issue 7.10 is canonical.\n", "BOM Issue 7.11 is the BOM in force.\n", + "The canonical BOM is Issue 7.11.\n"): self.assertEqual(self.ids("docs/a.md", text, "x"), ["BOM-ISSUE-IN-FORCE"], text) - for text in ("BOM Issue 7.10 is canonical.\n", "Issue 7.8 was canonical until Issue 7.10 superseded it.\n", - "The earlier canonical BOM was Issue 7.8.\n"): + for text in ("BOM Issue 7.14 is canonical.\n", "Issue 7.10 was canonical until Issue 7.14 superseded it.\n", + "The earlier canonical BOM was Issue 7.11.\n", "The canonical BOM is Issue 7.14.\n"): self.assertEqual(self.ids("docs/a.md", text, "x"), [], text) + by_id = {d["id"]: d for d in self.decisions} + retired = [s["value"] for s in by_id["BOM-ISSUE-IN-FORCE"]["supersedes"]] + self.assertIn("Issue 7.10", retired) + self.assertIn("Issue 7.11", retired) # P-03 rev19.2 item 20 (E-01 rev1.1): each check flags the stale wording and accepts a # sentence that labels it as superseded, removed or legacy. @@ -644,10 +651,63 @@ def test_base_camera_height_243_superseded(self): def test_icm42688p_is_the_2_0_imu(self): for text in ("The ICM-42688-P is selected as the OpenAMRobot 2.0 IMU.\n", - "ICM-42688-P (MIKROE-4237, SPI) is the baseline IMU; WHO_AM_I 0x47 at boot.\n"): + "ICM-42688-P (TDK EV_ICM-42688-P, SPI4) is the baseline IMU; WHO_AM_I 0x47 at boot.\n"): self.assertEqual(self.ids("docs/a.md", text, "x"), [], text) self.assertEqual(self.ids("docs/a.md", "The Gate A IMU is the MPU6050.\n", "x"), ["BASE-CONTROLLER-GATES"]) + # P-03 rev20.2 item 26: the TDK EV_ICM-42688-P replaces the MIKROE-4237 board. + def test_mikroe_4237_is_superseded(self): + for text in ("ICM-42688-P (MIKROE-4237, SPI) is the baseline IMU.\n", "IMU board: MIKROE-4237 on SPI4.\n"): + self.assertEqual(self.ids("docs/a.md", text, "x"), ["BASE-CONTROLLER-GATES"], text) + for text in ("MIKROE-4237 is superseded by the TDK EV_ICM-42688-P.\n", + "The EV_ICM-42688-P replaces the MIKROE-4237 (same sensor).\n", + "Historical: the IMU was on a MIKROE-4237.\n"): + self.assertEqual(self.ids("docs/a.md", text, "x"), [], text) + # Robot-description files are covered by FRAMES-REP105, without a duplicate finding. + self.assertEqual(self.ids("urdf/base.urdf.xacro", "\n"), + ["FRAMES-REP105"]) + self.assertEqual(self.ids("urdf/base.urdf.xacro", "\n"), []) + by_id = {d["id"]: d for d in self.decisions} + self.assertTrue(any("EV_ICM-42688-P" in v for v in by_id["FRAMES-REP105"]["values"])) + self.assertTrue(any("SPI4" in v and "EV_ICM-42688-P" in v for v in by_id["BASE-CONTROLLER-GATES"]["values"])) + + # P-03 rev20.2 item 26: values recorded without a new pattern. + def test_rev20_2_values_in_force(self): + data = yaml.safe_load((ROOT / "decisions.yaml").read_text(encoding="utf-8")) + self.assertEqual(data["in_force"], {"source": "P-03-rev20.2", "date": cd.as_date("2026-11-01")}) + by_id = {d["id"]: d for d in self.decisions} + self.assertTrue(any("DDR-240B-24" in v and "F-LIFT 15 A" in v for v in by_id["POWER-RAILS"]["values"])) + self.assertTrue(any("6 Nm holding" in v for v in by_id["DRIVETRAIN"]["values"])) + self.assertNotIn("pending", by_id["DRIVETRAIN"]["fix_hint"]) + self.assertTrue(any("holding test on the delivered unit" in v for v in by_id["LIFT"]["values"])) + self.assertIn("not a configuration ID", by_id["CONFIG-IDS"]["note"]) + for did in ("BOM-ISSUE-IN-FORCE", "FRAMES-REP105", "BASE-CONTROLLER-GATES", "DRIVETRAIN", "LIFT"): + self.assertTrue(by_id[did]["supersedes"], did) + self.assertEqual((by_id["COMPONENT-AVAILABILITY"]["kind"], by_id["COMPONENT-AVAILABILITY"]["owner"]), + ("limit", "platform-lead")) + self.assertEqual(by_id["TABLET-MOUNT"]["kind"], "configuration") + + def test_component_availability(self): + for name in ("docs/BOM.md", "hw/procurement/parts.yaml", "bom/bom_2_0.yml"): + self.assertEqual(self.ids(name, "PSU-01 single-source part, selected.\n", "x"), + ["COMPONENT-AVAILABILITY"], name) + for text in ("PSU-01 single-source part, named option next to PSU-02 (second-sourced).\n", + "Selected: PSU-02; single-source alternative PSU-01 as an option.\n", + "PSU-01 is single-source and not selected.\n"): + self.assertEqual(self.ids("docs/BOM.md", text, "x"), [], text) + # Outside BOM and procurement files the rule is not scanned. + self.assertEqual(self.ids("docs/a.md", "PSU-01 single-source part, selected.\n", "x"), []) + + def test_tablet_mount(self): + for text in ("The ZX80 tablet default position is portrait.\n", + "Portrait is the default for the tablet mount.\n", + "The tablet position is a configuration ID.\n"): + self.assertEqual(self.ids("docs/a.md", text, "x"), ["TABLET-MOUNT"], text) + for text in ("The ZX80 tablet default position is landscape; portrait is the second locked position.\n", + "Tablet mount: landscape (default, 234 x 149.8 mm) or portrait (149.8 x 234 mm).\n", + "The tablet position is a mount setting, not a configuration ID.\n"): + self.assertEqual(self.ids("docs/a.md", text, "x"), [], text) + def test_mpu6050_explanatory_gate_a_text_is_accepted(self): # Text explaining why the Gate A chip is not an MPU6050 is not a finding. for text in ("| `WHO_AM_I` (reg 0x75) | **0x70** (an MPU6050 would return 0x68; Gate A unit) |\n", @@ -763,10 +823,11 @@ 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. + def test_p03_rev20_1_values_are_cited(self): + # Owner decision 30 Oct 2026: P-03 rev20.1 values stay cited (30 degrees, hp30, ZX80 panel); + # P-03 rev20.2 is in force from 1 Nov 2026 (test_rev20_2_values_in_force). data = yaml.safe_load((ROOT / "decisions.yaml").read_text(encoding="utf-8")) - self.assertEqual(data["in_force"]["source"], "P-03-rev20.1") + self.assertEqual(data["in_force"]["source"], "P-03-rev20.2") 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"): @@ -825,14 +886,32 @@ def test_software_stop_is_base_only_and_not_an_estop(self): 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. + # P-03 rev20.1 item 24: Getac ZX80, 8 inch, 1920 x 1200; 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) + self.assertEqual(self.ids("docs/a.md", "Operator panel: Getac ZX10.\n", repo), ["OPERATOR-PANEL"]) 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) + by_id = {d["id"]: d for d in self.decisions} + self.assertIn("landscape by default with a portrait second position", by_id["OPERATOR-PANEL"]["value"]) + + def test_zx80_portrait_second_position_versus_default(self): + # P-03 rev20.2 item 26: portrait is the second locked position; only portrait as the default is flagged. + repo = "openamrobot-ui" + second = ("Layout for the ZX80 in portrait.\n", + "ZX80: landscape by default; portrait is the second locked position.\n", + "The ZX80 rotates to portrait (second locked position, 149.8 x 234 mm).\n") + default = ("The ZX80 default orientation is portrait.\n", + "Layout for the ZX80 in portrait by default.\n", + "Portrait is the default for the ZX80.\n") + for text in second: + self.assertEqual(self.ids("docs/a.md", text, repo), [], text) + self.assertEqual(self.ids("src/layout.ts", "// " + text, repo), [], text) + for text in default: + # One finding per line: TABLET-MOUNT in text files, OPERATOR-PANEL in UI code. + self.assertEqual(self.ids("docs/a.md", text, repo), ["TABLET-MOUNT"], text) + self.assertEqual(self.ids("src/layout.ts", "// " + text, repo), ["OPERATOR-PANEL"], 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).