From 41a5c86a8e3397698625b30ccfdc054ebc419d2c Mon Sep 17 00:00:00 2001 From: John Poole Date: Sat, 16 May 2026 15:46:53 -0700 Subject: [PATCH] safety, no data imported into sqlite yet --- migration/.gitignore | 1 + migration/Codex_prompt_20250616_1449.md | 47 +++ migration/Codex_response_20260616_1514.md | 154 +++++++ migration/README.md | 1 + migration/ble_symbols_20260516_1429.txt | 182 ++++++++ migration/reticulum_info_20260516_1444.txt | 41 ++ migration/scripts/import_codex_review.pl | 456 +++++++++++++++++++++ migration/sql/ble_migration_schema.sql | 58 +++ 8 files changed, 940 insertions(+) create mode 100644 migration/.gitignore create mode 100644 migration/Codex_prompt_20250616_1449.md create mode 100644 migration/Codex_response_20260616_1514.md create mode 100644 migration/README.md create mode 100644 migration/ble_symbols_20260516_1429.txt create mode 100644 migration/reticulum_info_20260516_1444.txt create mode 100644 migration/scripts/import_codex_review.pl create mode 100644 migration/sql/ble_migration_schema.sql diff --git a/migration/.gitignore b/migration/.gitignore new file mode 100644 index 0000000..9b1dffd --- /dev/null +++ b/migration/.gitignore @@ -0,0 +1 @@ +*.sqlite diff --git a/migration/Codex_prompt_20250616_1449.md b/migration/Codex_prompt_20250616_1449.md new file mode 100644 index 0000000..4246ef6 --- /dev/null +++ b/migration/Codex_prompt_20250616_1449.md @@ -0,0 +1,47 @@ +I am trying to extract the protocol-only portion of this BLE Reticulum implementation into a future C++ library callable from Python. + +Use /usr/local/src/ble-reticulum/src/ble_symbols_20260516_1429.txt as the master inventory. For every symbol listed in that file, inspect the corresponding source code and classify it as: + +[CORE] +Protocol logic that may belong in a future C++/Rust library. + +[GLUE] +Python Reticulum integration that should probably stay Python initially. + +[PLATFORM] +Linux/BLE/BlueZ/Bleak/bluezero/DBus/GATT/scanning/advertising code. + +[TEST] +Examples, proof scripts, diagnostics, command-line tools, or test harnesses. + +[UNKNOWN] +Not enough information to classify safely. + +For each symbol, produce a Markdown table with: + +- file path +- symbol name +- line number +- tag +- phase-1 C++ candidate: yes/no +- rationale +- important callers, if easily found +- important callees, if easily found + +Do not modify files. + +At the end, produce: + +1. A "Phase 1 C++ extraction candidates" list. +2. A "Do not port yet" list. +3. A short explanation of where the Python/C++ boundary should probably be. +4. Any symbols whose classification depends on Reticulum internals. + +For information about where reticulum is installed, see /usr/local/src/ble-reticulum/reticulum_info.txt + +The main tree is /usr/local/src/ble-reticulum which is pulled from my Forgejo instance. I have created a branch "c++migration" which is the current branch. + +Treat ble_dual_node_echo.py as an acceptance test/harness, not as the source of the protocol-core design. + +Prefer conservative classification. If a symbol touches RNS.Transport, RNS.Interface, RNS.Link, BleakClient, bluezero, DBus, sockets, threads, or OS-specific objects, do not mark it as phase-1 C++ unless the protocol logic can be cleanly separated from those dependencies. + \ No newline at end of file diff --git a/migration/Codex_response_20260616_1514.md b/migration/Codex_response_20260616_1514.md new file mode 100644 index 0000000..4d26d7f --- /dev/null +++ b/migration/Codex_response_20260616_1514.md @@ -0,0 +1,154 @@ +Read-only inspection completed. Note: the prompt’s Reticulum info path was not present as named; I used `/usr/local/src/ble-reticulum/reticulum_info_20260516_1444.txt`. Installed RNS is `rns 1.1.3` at `/home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS`. + +**BLEFragmentation.py** + +| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees | +|---|---:|---:|---|---|---|---|---| +| `src/ble_reticulum/BLEFragmentation.py` | `BLEFragmenter` | 52 | [CORE] | yes | Pure BLE packet fragmentation format; RNS only logging. | `BLEInterface._mtu_negotiated_callback`, `_handle_identity_handshake`, `BLEPeerInterface.process_outgoing` | `struct.pack` | +| same | `BLEFragmenter.__init__` | 68 | [CORE] | yes | MTU/header sizing logic. | fragmenter construction | none | +| same | `BLEFragmenter.fragment_packet` | 82 | [CORE] | yes | Core packet-to-fragments encoding. | `BLEPeerInterface.process_outgoing` | `struct.pack` | +| same | `BLEFragmenter.get_fragment_overhead` | 158 | [CORE] | yes | Pure sizing/overhead calculation. | tests/diagnostics likely | none | +| same | `BLEReassembler` | 176 | [CORE] | yes | Core fragment state machine; RNS only logging. | `BLEInterface` receive paths | `_reassemble`, `cleanup_stale_buffers` | +| same | `BLEReassembler.__init__` | 187 | [CORE] | yes | Protocol reassembly state. | reassembler construction | none | +| same | `BLEReassembler.receive_fragment` | 205 | [CORE] | yes | Core fragment validation/dedup/reassembly trigger. | `_handle_ble_data`, `handle_peripheral_data` | `struct.unpack`, `_reassemble` | +| same | `BLEReassembler._reassemble` | 380 | [CORE] | yes | Pure ordered fragment join. | `receive_fragment` | none | +| same | `BLEReassembler.cleanup_stale_buffers` | 402 | [CORE] | yes | Timeout policy for incomplete protocol frames. | cleanup timer, receive paths | `time.time` | +| same | `BLEReassembler.get_statistics` | 429 | [CORE] | yes | Protocol stats. | receive/logging paths | none | +| same | `BLEReassembler.reset_statistics` | 443 | [CORE] | yes | Protocol stats reset. | tests likely | none | +| same | `HDLCFramer` | 450 | [CORE] | yes | Pure byte-stuffing frame format. | not obviously used | static methods | +| same | `HDLCFramer.frame_packet` | 464 | [CORE] | yes | Pure HDLC-style escaping. | tests/possible future use | none | +| same | `HDLCFramer.deframe_packet` | 491 | [CORE] | yes | Pure HDLC-style unescaping/validation. | tests/possible future use | none | + +**BLEInterface.py** + +| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees | +|---|---:|---:|---|---|---|---|---| +| `src/ble_reticulum/BLEInterface.py` | `DiscoveredPeer` | 118 | [PLATFORM] | no | BLE discovery/connection scoring state. | `_device_discovered_callback`, `_select_peers_to_connect` | `time.time` | +| same | `DiscoveredPeer.__init__` | 146 | [PLATFORM] | no | Stores BLE address/RSSI/timing. | discovery callback | `time.time` | +| same | `update_rssi` | 167 | [PLATFORM] | no | Discovery cache update. | discovery callback | `time.time` | +| same | `record_connection_attempt` | 172 | [PLATFORM] | no | BLE connection policy state. | discovery callback | `time.time` | +| same | `record_connection_success` | 177 | [PLATFORM] | no | BLE connection policy state. | `_record_connection_success` | none | +| same | `record_connection_failure` | 181 | [PLATFORM] | no | BLE connection policy state. | `_record_connection_failure` | none | +| same | `get_success_rate` | 185 | [PLATFORM] | no | BLE peer scoring helper. | `_score_peer`, `__repr__` | none | +| same | `__repr__` | 196 | [TEST] | no | Diagnostic formatting only. | logging/debug | `get_success_rate` | +| same | `BLEInterface` | 202 | [GLUE] | no | Reticulum `Interface` implementation binding BLE driver to RNS Transport. | Reticulum external interface loader | many | +| same | `BLEInterface.__init__` | 267 | [GLUE] | no | Heavy RNS config, driver, locks, timers, callbacks. | Reticulum loader | `Interface.get_config_obj`, driver ctor | +| same | `start` | 479 | [GLUE] | no | Starts driver/scanning and touches Transport path workaround. | `__init__` | `driver.start`, `driver.start_scanning`, `_clear_stale_ble_paths` | +| same | `final_init` | 516 | [GLUE] | no | Reticulum lifecycle hook. | Reticulum | `_start_advertising_when_identity_ready` | +| same | `_setup_logging_redirect` | 529 | [GLUE] | no | RNS logging integration. | `__init__` | `logging`, `RNS.log` | +| same | `_start_advertising_when_identity_ready` | 583 | [GLUE] | no | Polls `RNS.Transport.identity`; starts platform advertising. | `final_init` thread | `driver.set_identity`, `driver.start_advertising` | +| same | `_clear_stale_ble_paths` | 630 | [GLUE] | no | Depends on `RNS.Transport.path_table` internals. | `start` | `Transport.path_table` | +| same | `_start_cleanup_timer` | 686 | [GLUE] | no | Thread timer around Python state. | `__init__`, cleanup task | `threading.Timer` | +| same | `_periodic_cleanup_task` | 701 | [GLUE] | no | Coordinates reassembly cleanup plus interface lifecycle. | timer | `cleanup_stale_buffers`, `_validate_spawned_interfaces` | +| same | `_cleanup_pending_identity_connections` | 741 | [GLUE] | no | Driver disconnect policy around identity handshake. | cleanup task | `driver.disconnect` | +| same | `_process_pending_detaches` | 771 | [GLUE] | no | Reticulum peer interface lifecycle. | cleanup task | `peer_if.detach`, `_get_fragmenter_key` | +| same | `_validate_spawned_interfaces` | 829 | [GLUE] | no | Compares RNS spawned interfaces to driver connections. | cleanup task | `driver.connected_peers`, `peer_if.detach` | +| same | `_device_discovered_callback` | 912 | [PLATFORM] | no | BLE scan callback and connection initiation. | driver callback | `DiscoveredPeer`, `_select_peers_to_connect`, `driver.connect` | +| same | `_device_connected_callback` | 964 | [GLUE] | no | Driver callback; maps identity to Reticulum peer interface. | driver callback | `_compute_identity_hash`, `_mtu_negotiated_callback` | +| same | `_check_duplicate_identity` | 1023 | [GLUE] | no | Identity dedup policy tied to driver state and MAC rotation. | driver callback | `_cleanup_stale_address`, `driver.disconnect` | +| same | `_mtu_negotiated_callback` | 1112 | [GLUE] | no | Creates protocol fragmenters but tied to driver/RNS interface spawn. | driver callback | `BLEFragmenter`, `BLEReassembler`, `_spawn_peer_interface` | +| same | `_handle_identity_handshake` | 1174 | [GLUE] | no | 16-byte identity handshake is protocol-ish, but intertwined with driver/RNS state. | `_data_received_callback` | `_check_duplicate_identity`, `_spawn_peer_interface` | +| same | `_data_received_callback` | 1281 | [GLUE] | no | Driver callback dispatch. | driver callback | `_handle_identity_handshake`, `_handle_ble_data` | +| same | `_device_disconnected_callback` | 1295 | [GLUE] | no | Reticulum/driver cleanup. | driver callback | `_compute_identity_hash`, detach scheduling | +| same | `_cleanup_stale_address` | 1380 | [GLUE] | no | MAC rotation mapping cleanup. | duplicate/address-change paths | dict cleanup | +| same | `_address_changed_callback` | 1411 | [GLUE] | no | Driver MAC rotation callback. | driver callback | `_get_fragmenter_key`, `_compute_identity_hash` | +| same | `_error_callback` | 1479 | [GLUE] | no | Driver error logging/blacklisting. | driver callback | `_record_connection_failure` | +| same | `_score_peer` | 1534 | [PLATFORM] | no | BLE RSSI/recency scoring policy. | `_select_peers_to_connect` | `peer.get_success_rate` | +| same | `_select_peers_to_connect` | 1620 | [PLATFORM] | no | BLE connection selection and MAC sorting. | discovery callback | `_score_peer`, `_is_blacklisted` | +| same | `_is_blacklisted` | 1761 | [PLATFORM] | no | BLE retry/backoff policy. | `_select_peers_to_connect` | `time.time` | +| same | `_record_connection_success` | 1785 | [PLATFORM] | no | BLE discovery/backoff state. | connection callback | `DiscoveredPeer.record_connection_success` | +| same | `_record_connection_failure` | 1800 | [PLATFORM] | no | BLE retry/backoff plus BlueZ cleanup hook. | error callback | `driver._remove_bluez_device` | +| same | `_get_fragmenter_key` | 1858 | [CORE] | yes | Pure identity-to-fragment-state key; cleanly separable. | many send/receive paths | `bytes.hex` | +| same | `_compute_identity_hash` | 1871 | [CORE] | yes | Pure identity truncation rule; cleanly separable. | identity mapping paths | `bytes.hex` | +| same | `_spawn_peer_interface` | 1892 | [GLUE] | no | Creates Reticulum `Interface`, mutates `RNS.Transport.interfaces`. | MTU/handshake paths | `BLEPeerInterface`, `RNS.Transport.interfaces.append` | +| same | `_handle_ble_data` | 1957 | [GLUE] | no | Uses core reassembly, but routes into Reticulum peer interfaces. | data callback | `BLEReassembler.receive_fragment`, `peer_if.process_incoming` | +| same | `handle_peripheral_data` | 2085 | [GLUE] | no | Older peripheral data path; handshake/reassembly/RNS routing mixed. | `BLEGATTServer` callback | `BLEFragmenter`, `BLEReassembler`, `_spawn_peer_interface` | +| same | `handle_central_connected` | 2188 | [GLUE] | no | GATT server to RNS interface lifecycle. | `BLEGATTServer` callback | `_spawn_peer_interface` | +| same | `handle_central_disconnected` | 2222 | [GLUE] | no | GATT server cleanup of Reticulum peer mapping. | `BLEGATTServer` callback | `_compute_identity_hash` | +| same | `process_incoming` | 2291 | [GLUE] | no | Calls Reticulum owner inbound. | peer interfaces | `owner.inbound` | +| same | `process_outgoing` | 2305 | [GLUE] | no | Broadcasts Reticulum packet to peer interfaces. | Reticulum Transport | `peer_if.process_outgoing` | +| same | `detach` | 2335 | [GLUE] | no | Reticulum/driver shutdown. | Reticulum | `driver.stop`, `peer_if.detach` | +| same | `should_ingress_limit` | 2365 | [GLUE] | no | Reticulum Interface policy. | Reticulum | none | +| same | `__str__` | 2376 | [TEST] | no | Diagnostic formatting. | logging | none | +| same | `BLEPeerInterface` | 2380 | [GLUE] | no | Reticulum per-peer interface wrapper. | `_spawn_peer_interface` | `Interface` | +| same | `BLEPeerInterface.__init__` | 2388 | [GLUE] | no | Reticulum interface fields/statistics. | `_spawn_peer_interface` | `Interface.__init__` | +| same | `BLEPeerInterface.process_incoming` | 2420 | [GLUE] | no | Hands data to `owner.inbound`. | `_handle_ble_data` | `owner.inbound` | +| same | `BLEPeerInterface.process_outgoing` | 2437 | [GLUE] | no | Calls core fragmenter, then platform driver send. | parent `process_outgoing`, Reticulum | `fragment_packet`, `driver.send` | +| same | `BLEPeerInterface.detach` | 2483 | [GLUE] | no | Removes interface from `RNS.Transport.interfaces`. | cleanup paths | `RNS.Transport.interfaces.remove` | +| same | `BLEPeerInterface.should_ingress_limit` | 2493 | [GLUE] | no | Delegates Reticulum policy. | Reticulum | parent method | +| same | `BLEPeerInterface.connection_id` | 2498 | [GLUE] | no | Reticulum/RNS identity display helper. | Reticulum/status | `RNS.Identity.full_hash` | +| same | `BLEPeerInterface.__str__` | 2510 | [TEST] | no | Diagnostic formatting. | logging | none | + +**BLEAgent.py, BLEGATTServer.py, bluetooth_driver.py** + +| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees | +|---|---:|---:|---|---|---|---|---| +| `src/ble_reticulum/BLEAgent.py` | `BLEAgent` + all methods | 55 | [PLATFORM] | no | BlueZ DBus pairing agent. | `register_agent`, GATT servers | `dbus.service`, `RNS.log` | +| same | `register_agent` | 202 | [PLATFORM] | no | Registers BlueZ agent on system DBus. | GATT server startup | `DBusGMainLoop`, `dbus.SystemBus` | +| same | `unregister_agent` | 262 | [PLATFORM] | no | Unregisters BlueZ agent. | GATT server shutdown | `dbus.SystemBus` | +| `src/ble_reticulum/BLEGATTServer.py` | `BLEGATTServer` + all methods | 51 | [PLATFORM] | no | bluezero GATT server, threads, callbacks, notifications. | older/alternate peripheral path | `bluezero.peripheral`, `register_agent` | +| same | `set_transport_identity` | 484 | [GLUE] | no | Validates Reticulum identity bytes but serves them over GATT. | startup | none | +| `src/ble_reticulum/bluetooth_driver.py` | `BLEDevice` | 10 | [PLATFORM] | no | BLE discovery DTO. | Linux driver scan | dataclass | +| same | `DriverState` | 18 | [PLATFORM] | no | Driver state enum. | driver state property | enum | +| same | `BLEDriverInterface` + all abstract methods | 29 | [PLATFORM] | no | Platform BLE backend contract, not protocol core. | `BLEInterface` | abstract methods | + +**linux_bluetooth_driver.py** + +| file path | symbol | line | tag | phase-1 C++ candidate | rationale | important callers | important callees | +|---|---:|---:|---|---|---|---|---| +| `src/ble_reticulum/linux_bluetooth_driver.py` | `_rns_showwarning` | 142 | [GLUE] | no | Routes Python warnings to RNS logging. | `warnings.showwarning` | `RNS.log` | +| same | `apply_bluez_services_resolved_patch` | 220 | [PLATFORM] | no | Monkey-patches Bleak BlueZ manager. | `LinuxBluetoothDriver.__init__` | `BlueZManager._wait_for_services_discovery` | +| same | `PeerConnection` | 287 | [PLATFORM] | no | BLE connection runtime DTO with `BleakClient`. | driver connection state | dataclass | +| same | `LinuxBluetoothDriver` + lifecycle/scanning/connection/GATT methods | 297 | [PLATFORM] | no | Linux BLE backend using Bleak, bluezero, DBus, asyncio, threads. | `BLEInterface` | `BleakScanner`, `BleakClient`, DBus, `BluezeroGATTServer` | +| same | `_connect_to_peer` | 988 | [PLATFORM] | no | Central connection flow, service discovery, GATT reads/writes. | `connect` | `BleakClient`, `_negotiate_mtu`, callbacks | +| same | `_connect_via_dbus_le` | 1253 | [PLATFORM] | no | BlueZ DBus LE-only workaround. | `_connect_to_peer` | `MessageBus`, `Adapter1.ConnectDevice` | +| same | `_negotiate_mtu` | 1291 | [PLATFORM] | no | Bleak/BlueZ MTU probing. | `_connect_to_peer` | `client._backend`, `client.mtu_size` | +| same | `_handle_notification` | 1332 | [PLATFORM] | no | Bleak notification callback adapter. | `start_notify` callback | `on_data_received` | +| same | `BluezeroGATTServer` + all methods | 1578 | [PLATFORM] | no | bluezero peripheral server, DBus monitoring, stale polling. | `LinuxBluetoothDriver.start_advertising` | `bluezero`, DBus, threads | +| same | `_handle_write_rx` | 2328 | [PLATFORM] | no | GATT write callback; forwards bytes upward. | bluezero | driver callbacks | +| same | `_handle_read_identity` | 2374 | [PLATFORM] | no | GATT identity characteristic read. | bluezero | none | +| same | `_handle_central_connected` | 2388 | [PLATFORM] | no | Peripheral connection state plus callbacks. | `_handle_write_rx` | `PeerConnection`, driver callbacks | +| same | `_handle_central_disconnected` | 2430 | [PLATFORM] | no | DBus/poll disconnect cleanup. | DBus monitor, stale poll | driver callback | +| same | `send_notification` | 2479 | [PLATFORM] | no | bluezero notification send. | `LinuxBluetoothDriver.send` | `tx_characteristic.set_value` | + +For `linux_bluetooth_driver.py`, the inventory includes many individual platform methods under `LinuxBluetoothDriver` and `BluezeroGATTServer`; all are [PLATFORM]/no for the same reason: they directly touch Bleak, bluezero, DBus, BlueZ, sockets/processes, asyncio event loops, or threads. + +**Phase 1 C++ Extraction Candidates** +- `BLEFragmenter` +- `BLEFragmenter.fragment_packet` +- `BLEFragmenter.get_fragment_overhead` +- `BLEReassembler` +- `BLEReassembler.receive_fragment` +- `BLEReassembler.cleanup_stale_buffers` +- `BLEReassembler.get_statistics` +- `BLEReassembler.reset_statistics` +- `HDLCFramer.frame_packet` +- `HDLCFramer.deframe_packet` +- Extracted pure equivalents of `BLEInterface._get_fragmenter_key` +- Extracted pure equivalents of `BLEInterface._compute_identity_hash` + +**Do Not Port Yet** +- `BLEInterface` and `BLEPeerInterface` as classes. +- All BlueZ/Bleak/bluezero/DBus code: `BLEAgent`, `BLEGATTServer`, `LinuxBluetoothDriver`, `BluezeroGATTServer`. +- `bluetooth_driver.py` driver abstraction unless designing a cross-language platform backend API later. +- Peer discovery/scoring/backoff/MAC-rotation orchestration in `BLEInterface`; it is BLE transport policy and Reticulum glue, not a clean protocol core. +- `ble_dual_node_echo.py`: treat as acceptance harness, as requested. + +**Likely Python/C++ Boundary** +The clean first boundary is a small protocol library that accepts/returns bytes and simple metadata only: fragment packet, parse fragment, reassemble by sender/session key, timeout stale buffers, and compute identity-derived keys. Python should continue owning Reticulum `Interface` integration, `RNS.Transport` mutation, driver callbacks, scanning, advertising, GATT characteristics, DBus pairing, threading, timers, and logging. + +**Classification Depends On Reticulum Internals** +- `BLEInterface.__init__`, `start`, `final_init` +- `_start_advertising_when_identity_ready` +- `_clear_stale_ble_paths` +- `_spawn_peer_interface` +- `_handle_ble_data` +- `handle_peripheral_data` +- `process_incoming` +- `process_outgoing` +- `detach` +- `BLEPeerInterface.process_incoming` +- `BLEPeerInterface.detach` +- `BLEPeerInterface.connection_id` + +The strongest dependency is `_clear_stale_ble_paths`, which assumes `RNS.Transport.path_table` tuple indexes and BLE interface type names. \ No newline at end of file diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 0000000..d74eb7d --- /dev/null +++ b/migration/README.md @@ -0,0 +1 @@ +This directory concerns the migration to C++ the ble-reticulum project. \ No newline at end of file diff --git a/migration/ble_symbols_20260516_1429.txt b/migration/ble_symbols_20260516_1429.txt new file mode 100644 index 0000000..93390ab --- /dev/null +++ b/migration/ble_symbols_20260516_1429.txt @@ -0,0 +1,182 @@ +BLEAgent.py + class BLEAgent line 55 + def __init__(self, bus, capability) line 68 + def _log(self, message, level) line 82 + def Release(self) line 101 + def AuthorizeService(self, device, uuid) line 111 + def RequestAuthorization(self, device) line 127 + def RequestConfirmation(self, device, passkey) line 139 + def RequestPasskey(self, device) line 155 + def Cancel(self) line 172 + def _format_device_path(self, device_path) line 182 + def register_agent(capability) line 202 + def unregister_agent(agent) line 262 +BLEFragmentation.py + class BLEFragmenter line 52 + def __init__(self, mtu) line 68 + def fragment_packet(self, packet) line 82 + def get_fragment_overhead(self, packet_size) line 158 + class BLEReassembler line 176 + def __init__(self, timeout) line 187 + def receive_fragment(self, fragment, sender_id) line 205 + def _reassemble(self, buffer) line 380 + def cleanup_stale_buffers(self) line 402 + def get_statistics(self) line 429 + def reset_statistics(self) line 443 + class HDLCFramer line 450 + def frame_packet(packet) line 464 + def deframe_packet(frame) line 491 +BLEGATTServer.py + class BLEGATTServer line 51 + def __init__(self, interface, device_name, agent_capability) line 77 + def _log(self, message, level) line 127 + def _handle_write_rx(self, value, options) line 148 + def _handle_read_identity(self, options) line 215 + def _handle_central_connected(self, central_address, mtu) line 242 + def _handle_central_disconnected(self, central_address) line 279 + def _run_server_thread(self) line 306 + async def start(self) line 441 + def set_transport_identity(self, identity_hash) line 484 + async def stop(self) line 503 + async def send_notification(self, data, central_address) line 543 + def is_connected(self, central_address) line 607 + def get_connected_centrals(self) line 620 + def get_connection_info(self, central_address) line 630 + def get_central_mtu(self, central_address) line 643 + def get_statistics(self) line 658 + def __str__(self) line 678 + def __repr__(self) line 684 +BLEInterface.py + class DiscoveredPeer line 118 + def __init__(self, address, name, rssi) line 146 + def update_rssi(self, rssi) line 167 + def record_connection_attempt(self) line 172 + def record_connection_success(self) line 177 + def record_connection_failure(self) line 181 + def get_success_rate(self) line 185 + def __repr__(self) line 196 + class BLEInterface line 202 + def __init__(self, owner, configuration) line 267 + def start(self) line 479 + def final_init(self) line 516 + def _setup_logging_redirect(self) line 529 + def _start_advertising_when_identity_ready(self) line 583 + def _clear_stale_ble_paths(self) line 630 + def _start_cleanup_timer(self) line 686 + def _periodic_cleanup_task(self) line 701 + def _cleanup_pending_identity_connections(self) line 741 + def _process_pending_detaches(self) line 771 + def _validate_spawned_interfaces(self) line 829 + def _device_discovered_callback(self, device) line 912 + def _device_connected_callback(self, address, peer_identity) line 964 + def _check_duplicate_identity(self, address, peer_identity) line 1023 + def _mtu_negotiated_callback(self, address, mtu) line 1112 + def _handle_identity_handshake(self, address, data) line 1174 + def _data_received_callback(self, address, data) line 1281 + def _device_disconnected_callback(self, address) line 1295 + def _cleanup_stale_address(self, identity_hash, old_address) line 1380 + def _address_changed_callback(self, old_address, new_address, identity_hash) line 1411 + def _error_callback(self, severity, message, exc) line 1479 + def _score_peer(self, peer) line 1534 + def _select_peers_to_connect(self) line 1620 + def _is_blacklisted(self, address) line 1761 + def _record_connection_success(self, address) line 1785 + def _record_connection_failure(self, address) line 1800 + def _get_fragmenter_key(self, peer_identity, peer_address) line 1858 + def _compute_identity_hash(self, peer_identity) line 1871 + def _spawn_peer_interface(self, address, name, peer_identity, client, mtu, connection_type) line 1892 + def _handle_ble_data(self, peer_address, data) line 1957 + def handle_peripheral_data(self, data, sender_address) line 2085 + def handle_central_connected(self, address) line 2188 + def handle_central_disconnected(self, address) line 2222 + def process_incoming(self, data) line 2291 + def process_outgoing(self, data) line 2305 + def detach(self) line 2335 + def should_ingress_limit(self) line 2365 + def __str__(self) line 2376 + class BLEPeerInterface line 2380 + def __init__(self, parent, peer_address, peer_name, peer_identity) line 2388 + def process_incoming(self, data) line 2420 + def process_outgoing(self, data) line 2437 + def detach(self) line 2483 + def should_ingress_limit(self) line 2493 + def connection_id(self) line 2498 + def __str__(self) line 2510 +__init__.py +bluetooth_driver.py + class BLEDevice line 10 + class DriverState line 18 + class BLEDriverInterface line 29 + def start(self, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid) line 57 + def stop(self) line 66 + def set_identity(self, identity_bytes) line 74 + def state(self) line 85 + def connected_peers(self) line 91 + def start_scanning(self) line 98 + def stop_scanning(self) line 106 + def start_advertising(self, device_name, identity) line 111 + def stop_advertising(self) line 124 + def connect(self, address) line 129 + def disconnect(self, address) line 137 + def send(self, address, data) line 142 + def read_characteristic(self, address, char_uuid) line 157 + def write_characteristic(self, address, char_uuid, data) line 165 + def start_notify(self, address, char_uuid, callback) line 173 + def get_local_address(self) line 183 + def get_peer_role(self, address) line 191 + def set_service_discovery_delay(self, seconds) line 204 + def set_power_mode(self, mode) line 212 +linux_bluetooth_driver.py + def _rns_showwarning(message, category, filename, lineno, file, line) line 142 + def apply_bluez_services_resolved_patch() line 220 + class PeerConnection line 287 + class LinuxBluetoothDriver line 297 + def __init__(self, discovery_interval, connection_timeout, min_rssi, service_discovery_delay, max_peers, adapter_index, agent_capability) line 314 + def _log(self, message, level) line 403 + def start(self, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid) line 426 + def stop(self) line 489 + def set_identity(self, identity_bytes) line 531 + def state(self) line 551 + def connected_peers(self) line 556 + def start_scanning(self) line 565 + def stop_scanning(self) line 582 + def _should_pause_scanning(self) line 593 + async def _scan_loop(self) line 607 + async def _perform_scan(self) line 636 + def start_advertising(self, device_name, identity) line 749 + def stop_advertising(self) line 784 + def connect(self, address) line 806 + def disconnect(self, address) line 876 + def _handle_peripheral_disconnected(self, address) line 909 + async def _remove_bluez_device(self, address) line 942 + async def _connect_to_peer(self, address) line 988 + async def _connect_via_dbus_le(self, peer_address) line 1253 + async def _negotiate_mtu(self, client) line 1291 + def _handle_notification(self, address, data) line 1332 + def send(self, address, data) line 1344 + def read_characteristic(self, address, char_uuid) line 1386 + def write_characteristic(self, address, char_uuid, data) line 1409 + def start_notify(self, address, char_uuid, callback) line 1431 + def get_local_address(self) line 1464 + def get_peer_role(self, address) line 1468 + def get_peer_mtu(self, address) line 1475 + def set_service_discovery_delay(self, seconds) line 1494 + def set_power_mode(self, mode) line 1499 + def _run_event_loop(self) line 1511 + async def _get_local_adapter_address(self) line 1523 + def _detect_bluez_version(self) line 1556 + class BluezeroGATTServer line 1578 + def __init__(self, driver, service_uuid, rx_char_uuid, tx_char_uuid, identity_char_uuid, adapter_index, agent_capability) line 1589 + def _log(self, message, level) line 1646 + def set_identity(self, identity_bytes) line 1650 + def _verify_services_on_dbus(self, timeout) line 1662 + def _monitor_device_disconnections(self) line 1730 + def _poll_stale_connections(self) line 1965 + def start(self, device_name) line 2072 + def stop(self) line 2160 + def _run_server_thread(self, device_name) line 2206 + def _handle_write_rx(self, value, options) line 2328 + def _handle_read_identity(self, options) line 2374 + def _handle_central_connected(self, central_address, mtu) line 2388 + def _handle_central_disconnected(self, central_address) line 2430 + def send_notification(self, central_address, data) line 2479 diff --git a/migration/reticulum_info_20260516_1444.txt b/migration/reticulum_info_20260516_1444.txt new file mode 100644 index 0000000..f5358d7 --- /dev/null +++ b/migration/reticulum_info_20260516_1444.txt @@ -0,0 +1,41 @@ +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ date +Sat May 16 14:41:10 PDT 2026 +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ python3 -c 'import RNS; print(RNS.__file__)' +/home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS/__init__.py +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnsd +/home/jlpoole/rnsenv/bin/rnsd +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnstatus +/home/jlpoole/rnsenv/bin/rnstatus +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ which rnpath +/home/jlpoole/rnsenv/bin/rnpath +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ equery belongs "$(which rnsd)" + * Searching for /home/jlpoole/rnsenv/bin/rnsd ... +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ python3 -m pip show rns +python3 -m pip show reticulum +Name: rns +Version: 1.1.3 +Summary: Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between +Home-page: https://reticulum.network/ +Author: Mark Qvist +Author-email: mark@unsigned.io +License: Reticulum License +Location: /home/jlpoole/rnsenv/lib/python3.13/site-packages +Requires: cryptography, pyserial +Required-by: lxmf, lxst, nomadnet, sbapp +WARNING: Package(s) not found: reticulum +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ date +hostname +which python3 +python3 -V +python3 -c 'import RNS, os; print("RNS file:", RNS.__file__); print("RNS dir :", os.path.dirname(RNS.__file__))' +which rnsd +rnsd --version 2>/dev/null || true +Sat May 16 14:43:28 PDT 2026 +jp +/home/jlpoole/rnsenv/bin/python3 +Python 3.13.12 +RNS file: /home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS/__init__.py +RNS dir : /home/jlpoole/rnsenv/lib/python3.13/site-packages/RNS +/home/jlpoole/rnsenv/bin/rnsd +rnsd 1.1.3 +(rnsenv) jlpoole@jp /usr/local/src/ble-reticulum $ diff --git a/migration/scripts/import_codex_review.pl b/migration/scripts/import_codex_review.pl new file mode 100644 index 0000000..af2dd4c --- /dev/null +++ b/migration/scripts/import_codex_review.pl @@ -0,0 +1,456 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Getopt::Long qw(GetOptions); +use File::Basename qw(dirname); +use Cwd qw(abs_path); + +my $script_dir = dirname(abs_path($0)); +my $default_db = "$script_dir/ble_migration.sqlite"; +my $default_md = "$script_dir/Codex_response_20260616_1514.md"; +my $default_inventory = "$script_dir/ble_symbols_20260516_1429.txt"; + +my $db_path = $default_db; +my $markdown_path = $default_md; +my $inventory_path = $default_inventory; +my $reviewer = 'codex'; +my $dry_run = 0; +my $help = 0; + +GetOptions( + 'db=s' => \$db_path, + 'markdown=s' => \$markdown_path, + 'inventory=s' => \$inventory_path, + 'reviewer=s' => \$reviewer, + 'dry-run' => \$dry_run, + 'help' => \$help, +) or usage(2); + +usage(0) if $help; + +die "Markdown file not found: $markdown_path\n" unless -f $markdown_path; +die "SQLite database not found: $db_path\n" unless -f $db_path; +die "Inventory file not found: $inventory_path\n" unless -f $inventory_path; + +my $inventory = load_inventory($inventory_path); +my @rows = parse_markdown_tables($markdown_path, $inventory); +die "No symbol rows found in $markdown_path\n" unless @rows; + +my $sql = build_sql(\@rows, $reviewer); + +if ($dry_run) { + print $sql; + exit 0; +} + +open(my $sqlite, '|-', 'sqlite3', $db_path) + or die "Unable to start sqlite3 for $db_path: $!\n"; +print {$sqlite} $sql; +close($sqlite) or die "sqlite3 import failed for $db_path\n"; + +print "Imported " . scalar(@rows) . " symbol review rows into $db_path\n"; + +sub usage { + my ($exit_code) = @_; + print <<"USAGE"; +Usage: + perl migration/import_codex_review.pl [options] + +Options: + --db PATH SQLite database path + default: migration/ble_migration.sqlite + --markdown PATH Codex Markdown review path + default: migration/Codex_response_20260616_1514.md + --inventory PATH symbol inventory used to infer class_name for unqualified + method rows + default: migration/ble_symbols_20260516_1429.txt + --reviewer NAME reviewer value for reviews table + default: codex + --dry-run print SQL instead of applying it + --help show this help + +The importer is idempotent for symbols: it upserts by the schema's unique key +(source_file, class_name, symbol_name, line_number). It appends one review row +per imported Markdown table row each time it is run. +USAGE + exit $exit_code; +} + +sub load_inventory { + my ($path) = @_; + open(my $fh, '<', $path) or die "Unable to read $path: $!\n"; + + my %inventory; + my $current_file; + my $current_class = ''; + + while (my $line = <$fh>) { + chomp $line; + + if ($line =~ /^(\S+\.py)\s*$/) { + $current_file = $1; + $current_class = ''; + next; + } + + next unless defined $current_file; + + if ($line =~ /^\s{4}class\s+([A-Za-z_][A-Za-z0-9_]*)\s+line\s+(\d+)/) { + my ($class, $line_number) = ($1, $2); + $current_class = $class; + my $key = inventory_key($current_file, $class, $line_number); + $inventory{$key} = { + symbol_type => 'class', + class_name => '', + symbol_name => $class, + }; + next; + } + + if ($line =~ /^\s{8}(?:async\s+)?def\s+([A-Za-z_][A-Za-z0-9_]*)\([^)]*\)\s+line\s+(\d+)/) { + my ($method, $line_number) = ($1, $2); + my $key = inventory_key($current_file, $method, $line_number); + $inventory{$key} = { + symbol_type => 'method', + class_name => $current_class, + symbol_name => $method, + }; + next; + } + + if ($line =~ /^\s{4}def\s+([A-Za-z_][A-Za-z0-9_]*)\([^)]*\)\s+line\s+(\d+)/) { + my ($function, $line_number) = ($1, $2); + my $key = inventory_key($current_file, $function, $line_number); + $inventory{$key} = { + symbol_type => 'function', + class_name => '', + symbol_name => $function, + }; + next; + } + } + + close($fh); + return \%inventory; +} + +sub inventory_key { + my ($file, $symbol, $line_number) = @_; + return join('|', basename_only($file), $symbol, defined($line_number) ? $line_number : ''); +} + +sub parse_markdown_tables { + my ($path, $inventory) = @_; + open(my $fh, '<', $path) or die "Unable to read $path: $!\n"; + + my @rows; + my $current_file; + my $in_expected_table = 0; + + while (my $line = <$fh>) { + chomp $line; + + if ($line =~ /^\|\s*file path\s*\|\s*symbol\s*\|\s*line\s*\|\s*tag\s*\|\s*phase-1 C\+\+ candidate\s*\|/i) { + $in_expected_table = 1; + next; + } + + if ($in_expected_table && $line =~ /^\|\s*-+/) { + next; + } + + if ($in_expected_table && $line !~ /^\|/) { + $in_expected_table = 0; + next; + } + + next unless $in_expected_table; + + my @cells = split_markdown_row($line); + next unless @cells == 8; + + my ($file, $symbol, $line_number, $tag, $candidate, $rationale, $callers, $callees) = @cells; + next if lc($file) eq 'file path'; + + $file = clean_cell($file); + if ($file eq 'same') { + die "Encountered 'same' file before an explicit file path\n" unless defined $current_file; + $file = $current_file; + } else { + $current_file = $file; + } + + $symbol = clean_cell($symbol); + $tag = clean_tag(clean_cell($tag)); + $candidate = lc(clean_cell($candidate)) eq 'yes' ? 1 : 0; + $line_number = clean_cell($line_number); + $line_number = $line_number =~ /(\d+)/ ? $1 : undef; + + $rationale = clean_cell($rationale); + $callers = clean_cell($callers); + $callees = clean_cell($callees); + + my ($symbol_type, $class_name, $symbol_name); + my $inventory_key = inventory_key($file, symbol_lookup_name($symbol), $line_number); + if (exists $inventory->{$inventory_key}) { + $symbol_type = $inventory->{$inventory_key}->{symbol_type}; + $class_name = $inventory->{$inventory_key}->{class_name}; + $symbol_name = $inventory->{$inventory_key}->{symbol_name}; + } else { + ($symbol_type, $class_name, $symbol_name) = infer_symbol_shape($symbol); + $class_name = '' unless defined $class_name; + } + my $phase = $candidate ? '1_candidate' : '0_inventory'; + my $confidence = $symbol =~ /\+\s*all\s+methods/i ? 'medium' : 'high'; + my $notes = "Imported from " . basename_only($path) . "; original symbol cell: $symbol"; + + push @rows, { + source_file => $file, + symbol_name => $symbol_name, + symbol_type => $symbol_type, + class_name => $class_name, + line_number => $line_number, + tag => $tag, + phase => $phase, + status => 'REVIEWED', + cpp_candidate => $candidate, + confidence => $confidence, + rationale => $rationale, + callers => $callers, + callees => $callees, + notes => $notes, + }; + } + + close($fh); + return @rows; +} + +sub split_markdown_row { + my ($line) = @_; + $line =~ s/^\|//; + $line =~ s/\|$//; + my @cells = split /\|/, $line, -1; + for my $cell (@cells) { + $cell =~ s/^\s+//; + $cell =~ s/\s+$//; + } + return @cells; +} + +sub clean_cell { + my ($value) = @_; + $value = '' unless defined $value; + $value =~ s//\n/gi; + $value =~ s/`([^`]*)`/$1/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + return $value; +} + +sub clean_tag { + my ($tag) = @_; + $tag =~ s/^\[//; + $tag =~ s/\]$//; + $tag = uc($tag || 'UNKNOWN'); + return $tag =~ /^(CORE|GLUE|PLATFORM|TEST|UNKNOWN)$/ ? $tag : 'UNKNOWN'; +} + +sub infer_symbol_shape { + my ($symbol) = @_; + + if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\s*\+\s*all\s+methods\b/i) { + return ('class_group', undef, $symbol); + } + + if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/) { + return ('method', $1, $2); + } + + if ($symbol =~ /^_[A-Za-z_][A-Za-z0-9_]*$/ || $symbol =~ /^[a-z_][A-Za-z0-9_]*$/) { + return ('function', undef, $symbol); + } + + if ($symbol =~ /^[A-Z][A-Za-z0-9_]*$/) { + return ('class', undef, $symbol); + } + + return ('symbol', undef, $symbol); +} + +sub symbol_lookup_name { + my ($symbol) = @_; + if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/) { + return $2; + } + if ($symbol =~ /^([A-Za-z_][A-Za-z0-9_]*)\s*\+\s*all\s+methods\b/i) { + return $1; + } + return $symbol; +} + +sub build_sql { + my ($rows, $reviewer_name) = @_; + + my $sql = <<"SQL"; +.bail on +PRAGMA foreign_keys = ON; +BEGIN; + +CREATE TEMP TABLE import_symbols ( + import_order INTEGER PRIMARY KEY, + source_file TEXT NOT NULL, + symbol_name TEXT NOT NULL, + symbol_type TEXT, + class_name TEXT, + line_number INTEGER, + tag TEXT NOT NULL, + phase TEXT NOT NULL, + status TEXT NOT NULL, + cpp_candidate INTEGER NOT NULL, + confidence TEXT, + rationale TEXT, + callers TEXT, + callees TEXT, + notes TEXT +); + +SQL + + my $i = 0; + for my $row (@{$rows}) { + $i++; + $sql .= "INSERT INTO import_symbols VALUES (" + . join(', ', + $i, + sql_quote($row->{source_file}), + sql_quote($row->{symbol_name}), + sql_quote($row->{symbol_type}), + sql_quote($row->{class_name}), + sql_integer($row->{line_number}), + sql_quote($row->{tag}), + sql_quote($row->{phase}), + sql_quote($row->{status}), + $row->{cpp_candidate} ? 1 : 0, + sql_quote($row->{confidence}), + sql_quote($row->{rationale}), + sql_quote($row->{callers}), + sql_quote($row->{callees}), + sql_quote($row->{notes}), + ) + . ");\n"; + } + + $sql .= <<"SQL"; + +CREATE TEMP TABLE import_existing AS +SELECT + i.import_order, + s.symbol_id, + s.tag AS old_tag, + s.status AS old_status +FROM import_symbols i +JOIN symbols s + ON s.source_file = i.source_file + AND COALESCE(s.class_name, '') = COALESCE(i.class_name, '') + AND s.symbol_name = i.symbol_name + AND COALESCE(s.line_number, -1) = COALESCE(i.line_number, -1); + +INSERT INTO symbols ( + source_file, + symbol_name, + symbol_type, + class_name, + line_number, + tag, + phase, + status, + cpp_candidate, + confidence, + rationale, + callers, + callees, + notes, + updated_at +) +SELECT + source_file, + symbol_name, + symbol_type, + class_name, + line_number, + tag, + phase, + status, + cpp_candidate, + confidence, + rationale, + callers, + callees, + notes, + CURRENT_TIMESTAMP +FROM import_symbols +WHERE 1 +ON CONFLICT(source_file, class_name, symbol_name, line_number) DO UPDATE SET + symbol_type = excluded.symbol_type, + tag = excluded.tag, + phase = excluded.phase, + status = excluded.status, + cpp_candidate = excluded.cpp_candidate, + confidence = excluded.confidence, + rationale = excluded.rationale, + callers = excluded.callers, + callees = excluded.callees, + notes = excluded.notes, + updated_at = CURRENT_TIMESTAMP; + +INSERT INTO reviews ( + symbol_id, + reviewer, + old_tag, + new_tag, + old_status, + new_status, + note +) +SELECT + s.symbol_id, + @{[sql_quote($reviewer_name)]}, + e.old_tag, + i.tag, + e.old_status, + i.status, + 'Imported from Codex Markdown review. Rationale: ' || COALESCE(i.rationale, '') +FROM import_symbols i +JOIN symbols s + ON s.source_file = i.source_file + AND COALESCE(s.class_name, '') = COALESCE(i.class_name, '') + AND s.symbol_name = i.symbol_name + AND COALESCE(s.line_number, -1) = COALESCE(i.line_number, -1) +LEFT JOIN import_existing e + ON e.import_order = i.import_order; + +COMMIT; +SQL + + return $sql; +} + +sub sql_quote { + my ($value) = @_; + return 'NULL' unless defined $value; + $value =~ s/'/''/g; + return "'$value'"; +} + +sub sql_integer { + my ($value) = @_; + return 'NULL' unless defined $value && $value =~ /^\d+$/; + return $value; +} + +sub basename_only { + my ($path) = @_; + $path =~ s{.*/}{}; + return $path; +} diff --git a/migration/sql/ble_migration_schema.sql b/migration/sql/ble_migration_schema.sql new file mode 100644 index 0000000..4714ff2 --- /dev/null +++ b/migration/sql/ble_migration_schema.sql @@ -0,0 +1,58 @@ +-- 20260516 ChatGPT +-- $Header$ +-- +-- Example: +-- cd /usr/local/src/ble-reticulum/migration +-- sqlite3 ble_migration.sqlite ".read sql/ble_migration_schema.sql" +-- Verify +-- sqlite3 ble_migration.sqlite ".tables" +-- sqlite3 ble_migration.sqlite ".schema symbols" +-- +-- Purpose: +-- Track symbol-by-symbol migration status for BLE Reticulum protocol extraction. + +CREATE TABLE IF NOT EXISTS symbols ( + symbol_id INTEGER PRIMARY KEY AUTOINCREMENT, + source_file TEXT NOT NULL, + symbol_name TEXT NOT NULL, + symbol_type TEXT, + class_name TEXT, + line_number INTEGER, + tag TEXT DEFAULT 'UNKNOWN', + phase TEXT DEFAULT '0_inventory', + status TEXT DEFAULT 'DISCOVERED', + cpp_candidate INTEGER DEFAULT 0, + confidence TEXT DEFAULT 'unknown', + rationale TEXT, + callers TEXT, + callees TEXT, + notes TEXT, + first_seen_at TEXT DEFAULT CURRENT_TIMESTAMP, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP, + + UNIQUE(source_file, class_name, symbol_name, line_number) +); + +CREATE TABLE IF NOT EXISTS reviews ( + review_id INTEGER PRIMARY KEY AUTOINCREMENT, + symbol_id INTEGER NOT NULL REFERENCES symbols(symbol_id), + reviewed_at TEXT DEFAULT CURRENT_TIMESTAMP, + reviewer TEXT, + old_tag TEXT, + new_tag TEXT, + old_status TEXT, + new_status TEXT, + note TEXT +); + +CREATE INDEX IF NOT EXISTS symbols_tag_idx + ON symbols(tag); + +CREATE INDEX IF NOT EXISTS symbols_status_idx + ON symbols(status); + +CREATE INDEX IF NOT EXISTS symbols_phase_idx + ON symbols(phase); + +CREATE INDEX IF NOT EXISTS symbols_cpp_candidate_idx + ON symbols(cpp_candidate); \ No newline at end of file