2331f7ed14
v2-ci / unit-tests (push) Waiting to run
allowWrites is no longer inert: SqlDriver implements IWritable, default off. - Gates (outermost first): node Writable + WriteOperate (server), driver allowWrites, the tag's own TagConfig "writable": true (strict JSON boolean; anything else refuses the tag, and the deploy gate says why), and a model that names exactly one row — KeyValue, or WideRow by where-pair. topByTimestamp and Query are BadNotWritable. - SqlWritePlanner: the only statement a write can become is UPDATE <table> SET <value col> = @v WHERE <key col> = @k, built from the catalog-gate-canonical identifiers (dialect-quoted) with value and key bound; value-before-key marker order so positional ODBC binds correctly. No authored write SQL, INSERT, UPSERT or procedure. - Executed in a transaction that commits only when exactly one row was touched: 0 rows -> BadNoData (never inserts), >1 -> rolled back, BadConfigurationError, unknown count -> rolled back, BadNotSupported. Bounded by operationTimeout; provider errors -> BadCommunicationError; health untouched. - Discovery advertises Operate only where a write could succeed. - AdminUI: driver form's Allow writes switch is live; tag editor gains a per-tag write switch; model validates writable strictly (parity corpus). - Factory no longer warns "allowWrites ignored"; it logs whether writes are on. - Tests: SqlWriteTests (planner SQL/bind order/injection/coercion, every gate, readback, absent key, two-row rollback, gate-rejected tag, discovery); MySqlReadTests write legs against InnoDB (keyed row only, two-row rollback, server-side grant refusal, default-off). Seed adds a setpoints table and the one UPDATE grant. (lmxopcua#753) Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV