3.3 KiB
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. Plan PR: F4-a.
Decision
The FOCAS driver ships writes behind two independent opt-ins, both default off:
- Driver-level master switch —
FocasDriverOptions.Writes.Enabled, defaultfalse. When off, every entry in aWriteAsyncbatch short- circuits toBadNotWritablewith status textwrites disabled at driver level. The wire client is never touched. - Per-tag opt-in —
FocasTagDefinition.Writable, defaultfalse(flipped fromtruein F4-a). AWritable = falsetag returnsBadNotWritableeven 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 = trueby 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.