# Decisions Architecture-level decisions taken during the v2 implementation, captured once and referenced from feature docs / PR descriptions / ADR-style follow-ups. Each entry lists the decision, the alternatives we considered, and the rationale that tipped the call. ## FOCAS write-path opt-in **Issue:** [#268](https://github.com/dohertj2/lmxopcua/issues/268). **Plan PR:** F4-a. ### Decision The FOCAS driver ships writes behind two independent opt-ins, both default off: 1. **Driver-level master switch** — `FocasDriverOptions.Writes.Enabled`, default `false`. When off, every entry in a `WriteAsync` batch short- circuits to `BadNotWritable` with status text `writes disabled at driver level`. The wire client is never touched. 2. **Per-tag opt-in** — `FocasTagDefinition.Writable`, default `false` (flipped from `true` in F4-a). A `Writable = false` tag returns `BadNotWritable` even when the driver-level flag is on. `BadNotSupported` is reserved for kinds the wire client hasn't yet implemented; F4-b/c land actual macro / parameter / PMC writes that currently dispatch to `BadNotSupported` (or to `Good` against the F4-a fake) for unimplemented branches. ### Alternatives considered - **Always-on writes (the pre-F4-a default).** Rejected: a single misconfigured tag flipping `Writable = true` by accident would let an operator overwrite a CNC parameter from any OPC UA client. The two- opt-in posture means an accidental tag flip alone isn't enough. - **Driver-level switch only.** Rejected: doesn't protect against an operator with admin rights flipping the master switch to do bulk diag reads but inheriting write capability for tags that were intended read-only. - **Per-tag opt-in only.** Rejected: doesn't give the deployment an "all writes off" emergency lever — useful during a CNC commissioning where writes are unsafe across the board for a period. ### Rationale CNC writes are non-idempotent in the field's worst-case shape: feed overrides, M-code pulses, alarm acks, recipe-step advances. Two opt-ins is the cheapest defence-in-depth posture that still lets writes ship. Both default off so a fresh deployment is read-only — the explicit choice to enable writes lands at config time where it's reviewable, not at runtime where it's invisible. `WriteIdempotent` plumbs through `CapabilityInvoker.ExecuteWriteAsync` into the Polly retry pipeline; default `false` means failed writes are not auto-retried (plan decisions #44 / #45). Per-tag flip required for genuinely-idempotent writes. ### CLI carve-out `otopcua-focas-cli write` sets `Writes.Enabled = true` locally for the lifetime of one process and synthesises a `Writable = true` tag. The CLI is a per-operator direct-to-CNC tool — not a long-lived process bound to the central config DB. Configuring the server still requires both opt-ins to be set explicitly in the DriverInstance JSON. The bypass is documented in `docs/Driver.FOCAS.Cli.md` so operators understand the asymmetry. ### Migration Pre-F4-a deployments that relied on the `Writable = true` default need to add `"Writable": true` to every tag they intend to write + an enclosing `"Writes": { "Enabled": true }` block in their DriverInstance JSON. Bootstrap rows seeded before F4-a get `Writable = false` after upgrade — this is intentional; review-then-flip is the safer migration path.