bitspire/.claude/skills/hal-check.md
Padreug 723553522c docs: purge stale lamassu naming; fix the hal-check skill's provenance boundary
- @lamassu/clink import examples → @bitSpire/clink, the package's real name.
- machine-installation.md: the service user is `bitspire`, not `lamassu`
  (renamed in configuration.nix long ago; the doc never followed).
- README: clone aiolabs/bitspire, not lamassu-next; the fleet sentence
  claiming batm3/douro run `main` against Lightning.Pub was stale.
- nostr-check skill: table headers say bitSpire.
- hal-check skill: the boundary is c0b69d1, not v8.1.5 (CLAUDE.md corrected
  this 2026-07-04; the skill kept asserting the wrong tag), and the
  "forbidden operations" now reflect the recorded permission —
  reference over port, name the source commit — plus a rule born of the
  GTQ window: no value table without a test over it.

Deliberately kept: every `aiolabs/lamassu-next#NN` issue citation, the
provenance sections, "Ported from lamassu-machine" driver headers, and
the hardware names "Lamassu Sintra/Tejo/Douro" — those are the machines.
2026-10-09 21:58:55 +02:00

8.6 KiB

/hal-check — Hardware Abstraction Layer Agent

Purpose

Validate HAL driver implementations in packages/hal/ against published hardware protocol specs (JCM ID003, Fujitsu F56 DLE/STX, Puloon LCDM, MEI EBDS, etc.) and against the public-domain tree of lamassu-machine (commit c0b69d1 and earlier) — which is the last lamassu-machine release published under a fully-open license.

Provenance. Drivers in packages/hal/ derive from lamassu-machine up to commit c0b69d1 (2023-09-19, v8.6.0-beta.9), the last public-domain commit; a9234d124d added Lamassu's Appendix A licence the same day, so every 8.1.5+ tag is proprietary — the old "v8.1.5 is the boundary" was wrong. Since 2026-10-09 we hold permission to use the post-boundary code as prior art too (see CLAUDE.md → Provenance): reference over port, and name the source commit when a block is ported verbatim.

Language note. ADR-001 selected TypeScript-in-Electron over Rust-in-Tauri for the HAL. Earlier versions of this skill referenced Rust patterns; that's obsolete. All checks below are TypeScript-flavored.

Invocation

/hal-check [command] [driver]

Commands:

  • port — Validate that a driver matches its lamassu-machine reference (c0b69d1 tree unless the port names a later commit) (where the driver was ported from one)
  • protocol — Check protocol implementation against published vendor specs
  • safety — Type safety, error handling, hardware safety review
  • mock — Validate mock implementation completeness

Drivers:

  • Validators: id003, ccnet, cashflow_sc (EBDS), bnr_advance, genmega, hcm2, gsr50
  • Dispensers: puloon, f56, genmega, hcm2, gsr50
  • Printers: nippon, zebra, genmega

Porting validation (port command)

Source reference

Each TS driver in packages/hal/ maps to (at most) one JS source in lamassu-machine at c0b69d1 (the last public-domain commit):

TS Driver JS Source (c0b69d1 tree)
validators/id003/*.ts lib/id003/*.js
validators/ccnet/*.ts lib/ccnet/*.js
validators/ebds/*.ts lib/mei/cashflow_sc.js + lib/mei/*.js
dispensers/puloon/*.ts lib/puloon/*.js
dispensers/f56/*.ts lib/f56/*.js

If a driver in packages/hal/ references a JS source NOT under v8.1.5 (or no JS source at all because it was re-derived from vendor specs), the attribution comment at the top of the TS file should say so.

Porting checklist

/hal-check port id003

Validates:

  • All protocol commands implemented (RESET, ENABLE, DISABLE, STACK, RETURN, STATUS — see ID003 spec or lib/id003/id003rs232.js from v8.1.5)
  • State machine matches the JS FSM (lib/id003/id003fsm.js)
  • CRC/checksum calculation byte-identical to the JS version
  • Timeout values match the JS source
  • Error codes mapped correctly to TypeScript enum/union
  • Denomination tables match (currency-specific)
  • Attribution comment at top of TS file points at the specific v8.1.5 file

Protocol commands

ID003 (JCM)

Command Code JS Reference (v8.1.5)
RESET 0x40 id003rs232.js:reset()
ENABLE 0x13 id003fsm.js:enable
DISABLE 0x14 id003fsm.js:disable
STACK 0x15 id003fsm.js:stack
RETURN 0x16 id003fsm.js:return
STATUS 0x10 id003fsm.js:poll

Puloon LCDM

Command Code JS Reference (v8.1.5)
RESET 0x44 puloonrs232.js
DISPENSE 0x45 puloonrs232.js
STATUS 0x46 puloonrs232.js

Safety review (safety command)

Type safety

  • No any types in driver public API
  • Discriminated unions for state machine states (not just string literals)
  • Error types are typed Result<T, DriverError> (or equivalent) at the driver boundary — never throw raw strings
  • Buffer reads are bounds-checked; offsets validated before slicing

Concurrency safety

  • Serial port access is single-writer (TypeScript single-threaded helps, but ensure no two await write() calls can interleave with each other's expected response)
  • Event emitters are bounded (max listeners set; back-pressure handled)
  • Timeouts on all blocking I/O (Promise.race with setTimeout)
  • No unbounded queues — a runaway validator polling loop shouldn't fill RAM

Hardware safety

  • Dispenser amounts validated against cassette inventory before dispensing
  • Bill count cross-checked between requested and dispensed
  • Error states trigger hardware reset (disable + reset + enable cycle for validators)
  • Graceful degradation: partial dispense reports correct dispensed-vs-rejected counts
  • Validator goes to disable on app shutdown — bills inserted while the app is dead get returned, not stacked uncounted

Mock validation (mock command)

Mock requirements

Mocks under packages/hal/src/**/mock.ts must simulate:

  1. Normal operation flow
  2. Error conditions (jam, empty cassette, comms failure)
  3. Realistic timing (validator escrow takes ~600ms; dispenser cycle takes ~2s)
  4. State persistence within a session

Mock test coverage

/hal-check mock puloon

Validates mock implements:

  • connect() — success and failure paths
  • dispense() — full, partial, and over-requested paths
  • reset() — error recovery
  • Event emission timing roughly matches real hardware
  • Cassette state tracking (count decrements properly)

Protocol analysis (protocol command)

Packet structure validation

/hal-check protocol id003

Compares TS packet building with the v8.1.5 JS reference:

// TypeScript implementation
function buildPacket(data: Uint8Array): Uint8Array {
  const packet = new Uint8Array(data.length + 4)
  packet[0] = 0x02              // SYNC
  packet[1] = data.length + 4   // length includes header + CRC
  packet.set(data, 2)
  const crc = calculateCrc(packet.slice(0, -2))
  packet[packet.length - 2] = crc & 0xff
  packet[packet.length - 1] = (crc >> 8) & 0xff
  return packet
}

Against the v8.1.5 JS reference (line numbers may vary by tag):

// lamassu-machine c0b69d1 — lib/id003/id003rs232.js
function buildPacket(data) {
  const buf = Buffer.alloc(data.length + 4)
  buf[0] = 0x02 // SYNC
  buf[1] = data.length + 4
  data.copy(buf, 2)
  const crc = calculateCrc(buf.slice(0, -2))
  buf.writeUInt16LE(crc, buf.length - 2)
  return buf
}

A discrepancy here (different CRC polynomial, different framing, different endianness) is a serious port bug.

Output format

Port validation

## HAL Port Validation: id003

### Command coverage
| Command | v8.1.5 JS | TS | Match |
|---|---|---|---|
| RESET | ✅ | ✅ | ✅ |
| ENABLE | ✅ | ✅ | ✅ |
| STATUS | ✅ | ⚠️ | Partial |

### Protocol differences
- [ ] `id003-rs232.ts:78` — CRC uses different polynomial than v8.1.5 JS

### Missing implementations
- [ ] HOLD command not implemented (v8.1.5 `id003fsm.js:holdBill`)

### Attribution
- [x] File header points at `lib/id003/id003rs232.js` (v8.1.5)

Safety review

## HAL Safety Review: puloon

### Type safety
- [x] No `any` types in public surface
- [x] Discriminated union for DispenserState

### Concurrency safety
- [x] Single-writer over serial port
- [x] Event emitter has bounded listener count
- [ ] `puloon-rs232.ts:142` — no timeout on response wait; could hang forever

### Hardware safety
- [x] Amount validated against cassette inventory in dispense()
- [ ] `puloon.ts:178` — partial dispense doesn't update cassette inventory

### Critical issues
1. `puloon-rs232.ts:142` — wrap response wait in `Promise.race([response, timeout(5000)])`
2. `puloon.ts:178` — decrement cassette count by actual dispensed, not requested

Example usage

/hal-check port id003
/hal-check safety packages/hal/src/dispensers/puloon/
/hal-check mock --all
/hal-check protocol ebds      # validates EBDS framing against v8.1.5 JS + MEI vendor spec
  • /security — broader Bitcoin/Lightning/ATM vulnerability scan
  • /test — runs HAL-specific tests, generates coverage reports
  • /lightning-check — validates the Lightning backend side (LNbits transport)

Forbidden operations

  • Port a post-c0b69d1 block without naming its source commit in the commit message. The permission to reference that code is recorded in CLAUDE.md; the provenance of anything carried over must be recoverable from git log.
  • Copy a value table (note lengths, timings) without a test over it — bills.ts carried a wrong GTQ window for months precisely because nothing asserted it.
  • Include attribution comments pointing at v8.1.6+ files even if the implementation is your own — readers should be able to trust file-header attributions as accurate.