73
docs/v2/decisions.md
Normal file
73
docs/v2/decisions.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user