Files
CableGUI/internal/db/migrations/003_connection_requirements.sql
mAi d8637de4a0 feat(db): connection_requirements + cables.auto
Migration 003 adds the solver's per-project input table + the auto flag
that slice 6 will use to distinguish solver-owned cables from m's
hand-drawn ones.

connection_requirements:
- (from_device_id, to_device_id, preferred_cable_type_id) with
  preferred_cable_type_id nullable ("solver picks if exactly one type
  matches both ends").
- (pair_lo, pair_hi) is the order-normalised MIN/MAX of (from, to),
  stored alongside the m-facing from/to so the UI doesn't have to
  denormalise.
- UNIQUE (project_id, pair_lo, pair_hi, preferred_cable_type_id) →
  (A,B,T) and (B,A,T) collide; (A,B,Power) + (A,B,RJ45) coexist.
- CHECK (from != to). FK CASCADE from devices → requirement vanishes
  if either endpoint device is deleted.

Store + 11 new tests:
- pair normalisation rejects the reversed-direction duplicate
- different cable types on the same pair coexist
- self-loop rejected (ErrInvalidInput)
- cross-project device reference rejected
- two null-cable-type reqs on the same pair both succeed (SQLite NULL
  != NULL in UNIQUE — semantically "solver picks both times", second
  wins)
- partial PATCH: preferred_cable_type_id tri-state (leave/set/clear),
  must_connect bool, notes string
- device delete cascades to its requirements
- snapshot.connection_requirements is non-nil and populated

cables.auto:
- ALTER TABLE cables ADD COLUMN auto INTEGER NOT NULL DEFAULT 0 CHECK
  (auto IN (0,1)). Slice 6 sets 1 from the solver; slice 7's manual
  cable POST keeps the default 0.
2026-05-16 00:37:34 +02:00

35 lines
2.0 KiB
SQL

-- mCables v4.1 connection requirements + solver-owned cable flag.
-- See docs/design.md §2.1 + §2 connection_requirements + §5b.3.
-- The solver's input: "device A must connect to device B via cable type T".
-- Many per device. (from, to) is normalised on insert as
-- (pair_lo, pair_hi) = (MIN(from, to), MAX(from, to)) so (A,B,T) and (B,A,T)
-- can't coexist (UNIQUE enforces it).
CREATE TABLE connection_requirements (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
from_device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
to_device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
preferred_cable_type_id INTEGER REFERENCES cable_types(id) ON DELETE SET NULL,
must_connect INTEGER NOT NULL DEFAULT 1 CHECK (must_connect IN (0, 1)),
notes TEXT NOT NULL DEFAULT '',
pair_lo INTEGER NOT NULL,
pair_hi INTEGER NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
CHECK (from_device_id != to_device_id),
UNIQUE (project_id, pair_lo, pair_hi, preferred_cable_type_id)
);
CREATE INDEX conn_reqs_project_idx ON connection_requirements(project_id);
CREATE INDEX conn_reqs_pair_idx ON connection_requirements(project_id, pair_lo, pair_hi);
CREATE INDEX conn_reqs_from_idx ON connection_requirements(from_device_id);
CREATE INDEX conn_reqs_to_idx ON connection_requirements(to_device_id);
-- Solver-owned cable flag (§5b.3): 1 = the solver placed this cable,
-- replaceable on re-solve. 0 = m hand-drew it, left alone by the solver.
-- Slice 6 ships the solver that writes auto=1; slice 7 ships hand-drawn
-- cable creation that writes auto=0.
ALTER TABLE cables ADD COLUMN auto INTEGER NOT NULL DEFAULT 0
CHECK (auto IN (0, 1));
CREATE INDEX cables_auto_idx ON cables(auto);