Since #808 an UNS folder with no placement in the generation is denied (fail
closed), so the matrix's ad hoc "Eq" folder made every UNS case fail with
BadUserAccessDenied on the candidate server. The fixture now supplies
unsFolderScopes (+ the alarm's tagReferencingEquipment) as a deployed
generation does. An unplaced "Unplaced" UNS folder is kept as a negative
control: the Shared client leg requires it denied against a #808+ server with
the ACL on. No server authorization code changed.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
The cross-version matrix (#782) used a 55-char 'otua_matrix_' key, which stayed under
SDK 2's 64-byte RsaEncryptedSecret threshold and hid #880. It now uses the
ApiKeySecret.Generate shape (65 chars, random per run, never recorded). Every Shared
leg adds production-length-API-key-wrong-secret-reaches-key-check, which must get
BadIdentityTokenRejected rather than a format failure. SDK 2 clients also add
sdk2-default-RsaEncryptedSecret-control, the negative control: the SDK's default
handler must be refused with BadIdentityTokenInvalid by a 1.x server and admitted
by an SDK 2 server. The runner passes the server SDK version from ready.json.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
Probe 'prepare' provisions the consumer identity via OpcUaApplicationIdentity.EnsureApplicationCertificateAsync
and exports the public DER; runner installs it in the server trusted store before start. Adds Sign
(OPCUA_MATRIX_SERVER_SECURITY=sign), untrusted-client / untrusted-server fail-closed controls per secure
mode, and OPCUA_MATRIX_ACL=off. Autoaccept stays false everywhere.