@@ -43,3 +43,98 @@ The history projection emits each unseen entry through
|
||||
reported wall-clock — keep CNC clocks on UTC so the dedup key
|
||||
`(OccurrenceTime, AlarmNumber, AlarmType)` stays stable across DST
|
||||
transitions.
|
||||
|
||||
## Write safety — issue #269, plan PR F4-b
|
||||
|
||||
The FOCAS driver supports `cnc_wrparam` and `cnc_wrmacro` writes behind
|
||||
multiple independent opt-ins. A misdirected parameter write can put the
|
||||
CNC in a bad state, so the runbook below MUST be followed before flipping
|
||||
the granular kill switches on.
|
||||
|
||||
### Operator pre-checks (every deployment, every change)
|
||||
|
||||
1. **CNC must be in MDI mode.** Most parameter writes fail with `EW_PASSWD`
|
||||
(surfaces as `BadUserAccessDenied`) unless the CNC is in MDI. The
|
||||
server-side write returns immediately with the access-denied status; no
|
||||
value reaches the wire.
|
||||
2. **Parameter-write switch enabled on the CNC pendant.** Even in MDI mode
|
||||
protected parameters require the operator to physically enable the
|
||||
parameter-write switch. Without it `cnc_wrparam` returns `EW_PASSWD`.
|
||||
Plan PR F4-d will land an OPC UA-side unlock workflow; today the only
|
||||
path is the pendant.
|
||||
3. **Verify each tag's address against the FANUC manual.** Ranges vary per
|
||||
CNC series; the
|
||||
[`focas-version-matrix`](./focas-version-matrix.md) capability matrix
|
||||
rejects out-of-range numbers at startup, but address-vs-meaning is the
|
||||
operator's job.
|
||||
4. **Dry run with `Writable = true` but `Writes.AllowParameter = false`.**
|
||||
Staged opt-in catches mis-mapped tags: every PARAM write returns
|
||||
`BadNotWritable` until you flip the granular flag, so you can confirm
|
||||
the tag list before any wire write fires.
|
||||
|
||||
### LDAP group requirements
|
||||
|
||||
Per [`docs/security.md`](../security.md) the server-layer ACL maps
|
||||
`SecurityClassification` to LDAP groups. Post-F4-b:
|
||||
|
||||
| Tag kind | LDAP group required |
|
||||
| --- | --- |
|
||||
| `PARAM:N` (writable) | **`WriteConfigure`** — heaviest write tier; matches commissioning roles |
|
||||
| `MACRO:N` (writable) | `WriteOperate` — standard HMI recipe / setpoint group |
|
||||
| PMC R/G/F (writable) | `WriteOperate` |
|
||||
| Read-only | `ReadOnly` |
|
||||
|
||||
Per the `feedback_acl_at_server_layer` design note, the FOCAS driver
|
||||
declares the classification but does NOT enforce it; `DriverNodeManager`
|
||||
applies the gate before the driver's `WriteAsync` ever runs. A user
|
||||
without `WriteConfigure` who attempts a `PARAM:` write gets
|
||||
`BadUserAccessDenied` from the server with no driver-level audit entry —
|
||||
the OPC UA layer's audit log catches it.
|
||||
|
||||
### Audit-log expectations
|
||||
|
||||
Every successful write produces:
|
||||
|
||||
- An OPC UA AuditWriteEvent (server layer — see
|
||||
[`docs/security.md`](../security.md) "Audit logging").
|
||||
- A FOCAS driver-level Serilog entry tagged `Driver=FOCAS DriverInstanceId=...
|
||||
TagName=... Address=... ResultStatus=...`.
|
||||
- A `Writes/LastWriteAt` and `Writes/LastWriteStatus` diagnostic counter
|
||||
refresh on the device's `Diagnostics/` fixed-tree node (planned;
|
||||
populated as F4-c lands).
|
||||
|
||||
Failures to write (`BadUserAccessDenied`, `BadCommunicationError`, etc.)
|
||||
produce the same audit entries with the failure status code so a
|
||||
post-incident reviewer sees the same shape regardless of whether the write
|
||||
succeeded.
|
||||
|
||||
### Granular config example
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"Drivers": {
|
||||
"FOCAS": {
|
||||
"Devices": [
|
||||
{ "HostAddress": "focas://10.0.0.5:8193", "Series": "Series30i" }
|
||||
],
|
||||
"Writes": {
|
||||
"Enabled": true,
|
||||
"AllowMacro": true, // recipe / setpoint writes — operator role
|
||||
"AllowParameter": false // commissioning only — keep locked except during planned work
|
||||
},
|
||||
"Tags": [
|
||||
{ "Name": "Recipe.PartCount", "DeviceHostAddress": "focas://10.0.0.5:8193",
|
||||
"Address": "MACRO:500", "DataType": "Int32",
|
||||
"Writable": true, "WriteIdempotent": true },
|
||||
{ "Name": "MaxFeedrate", "DeviceHostAddress": "focas://10.0.0.5:8193",
|
||||
"Address": "PARAM:1815", "DataType": "Int32",
|
||||
"Writable": false /* keep read-only until commissioning window */ }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Flipping `AllowParameter` on for the commissioning window (and back off
|
||||
afterward) is the recommended deployment cadence — the granular kill
|
||||
switch is a lightweight runtime toggle, not a config-DB redeploy.
|
||||
|
||||
Reference in New Issue
Block a user