docs(deploy): vd03 upgrade-readiness pack b8b4c18f -> master (#793)
v2-ci / unit-tests (push) Waiting to run
v2-ci / unit-tests (push) Waiting to run
Read-only ConfigDb preflight (scripts/preflight/vd03-preupgrade-b8b4c18f-configdb-checks.sql) covering every deploy gate added since the deployed build: VirtualTag/Calculation effective triggers (#783), VT->VT reads and VirtualTagDependencyCycle (#791), newly forbidden script APIs (#792, E2), credential-reference namespace / other-instance / reserved-name rules (#815 #835 #836 #848 #849 #850), MTConnectMultipleDevices, Sql query/write gates (#781), Heidenhain, UNS effective-name collisions and the FOCAS renames/value changes. Verified on a throwaway b8b4c18f-schema database (every check fires on seeded rows) and clean against the docker-dev config. Upgrade notes (docs/deploy/vd03-upgrade-b8b4c18f-to-master.md): the EnableSnapshotIsolation migration and how to apply it by hand, the env-var audit (Security:ApiKeys:Pepper is now required and absent on the box; JWT key validated at start), SDK 2.0.1 / CA KeyUsage, the ScadaBridge-visible changes, swap/rollback and post-deploy checks. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -0,0 +1,680 @@
|
||||
/* =====================================================================================================
|
||||
vd03-preupgrade-b8b4c18f-configdb-checks.sql
|
||||
|
||||
Pre-upgrade ConfigDb checks for wonder-app-vd03: OtOpcUa b8b4c18f (2026-09-03, the build actually deployed —
|
||||
identified by PDB source-hash + deps.json package versions) -> master 363f0d81.
|
||||
Gitea lmxopcua#793. Companion notes: docs/deploy/vd03-upgrade-b8b4c18f-to-master.md
|
||||
|
||||
STRICTLY READ-ONLY. SELECT statements only, plus INSERTs into local TABLE VARIABLES (session-scoped, never
|
||||
written to this database). No DDL, no DML against any database table, no temp tables, no transactions.
|
||||
|
||||
Run it against the ConfigDb while the box is STILL on b8b4c18f, i.e. before the EnableSnapshotIsolation
|
||||
migration and before the new binaries start:
|
||||
|
||||
sqlcmd -S WONDER-APP-VD03 -d OtOpcUaConfig -E -i vd03-preupgrade-b8b4c18f-configdb-checks.sql -W -s "|" -o preflight.txt
|
||||
(or open it in SSMS with the OtOpcUaConfig database selected and run it; use "Results to Text" for one file)
|
||||
|
||||
The database name on vd03 is OtOpcUaConfig (docs/deployments/wonder-app-vd03-makino-z-34184.md); the
|
||||
script does not hard-code it (no USE), so select the right database first. Check 0 prints which one you hit.
|
||||
|
||||
Requirements: SQL Server 2016+ AND database compatibility level >= 130 (OPENJSON). Check 0 reports both.
|
||||
|
||||
Schema: written against the b8b4c18f EF model (last migration 20260731162342_AddApiKeyTable — identical to
|
||||
e82881c1; no migration landed between them). master adds only 20261022102459_EnableSnapshotIsolation (no
|
||||
table/column change), so the script also runs unchanged after the upgrade. Every rule checked here is new in
|
||||
b8b4c18f..master (DraftValidator / ForbiddenTypeAnalyzer were untouched between e82881c1 and b8b4c18f).
|
||||
|
||||
OUTPUT: every check emits two result sets
|
||||
(1) a one-row header: check id, title, status ('ok' | 'attention' | 'info'), row count, blocking?, source
|
||||
(2) the detail rows (empty when ok).
|
||||
'attention' + blocking = YES means master's deploy gate (DraftValidator / AdminOperationsActor) will REFUSE the
|
||||
next deploy until fixed. blocking = NO means a behaviour change or an authoring/runtime effect, not a refusal.
|
||||
|
||||
Secrets: no credential VALUE is ever selected. Credential fields are reported as a classification plus the
|
||||
reference NAME (secret:/env: names are not secrets); literals print as '<literal redacted>'.
|
||||
|
||||
Approximations (the C# rules use Roslyn-free regexes and System.Text.Json; T-SQL cannot reproduce them
|
||||
exactly — every approximation errs toward listing MORE rows, never fewer, except where noted):
|
||||
* ctx.GetTag("...") literal extraction normalises whitespace first (CR/LF/TAB -> space, runs collapsed,
|
||||
spaces around '.', '(' and the quotes removed). A path literal containing a double space would be
|
||||
mangled; a GetTag inside a comment or string is counted (the C# regex counts it too).
|
||||
* Cycle detection (check 4) is a bounded path search (depth <= 25) over the same graph DraftValidator
|
||||
builds; it can miss a cycle longer than 25 hops and reports each cycle once per VirtualTag member.
|
||||
* JSON: ISJSON/OPENJSON reject comments and trailing commas that System.Text.Json (with the policy's
|
||||
options) accepts, so such a blob shows as 'config is not valid JSON' here but may pass the gate.
|
||||
* The #850 merged driver+device check is approximated (see check 6).
|
||||
===================================================================================================== */
|
||||
|
||||
SET NOCOUNT ON;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Shared working sets (table variables)
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
|
||||
DECLARE @Num TABLE (n int PRIMARY KEY);
|
||||
WITH x AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM x WHERE n < 256)
|
||||
INSERT @Num (n) SELECT n FROM x OPTION (MAXRECURSION 256);
|
||||
|
||||
-- Scripts with a whitespace-normalised copy of the source for ctx.GetTag("...") extraction.
|
||||
DECLARE @Script TABLE (ScriptId nvarchar(64) PRIMARY KEY, Name nvarchar(128), Src nvarchar(max) COLLATE Latin1_General_BIN2);
|
||||
INSERT @Script (ScriptId, Name, Src)
|
||||
SELECT s.ScriptId, s.Name,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
|
||||
REPLACE(REPLACE(REPLACE(s.SourceCode, CHAR(13), ' '), CHAR(10), ' '), CHAR(9), ' ')
|
||||
, ' ', ' '), ' ', ' '), ' ', ' '), ' ', ' '), ' ', ' ')
|
||||
, ' .', '.'), '. ', '.'), 'GetTag (', 'GetTag('), 'GetTag( ', 'GetTag('), '" )', '")'), ' )', ')'), '( "', '("'), '( ', '(')
|
||||
FROM dbo.Script s
|
||||
WHERE s.ScriptId IS NOT NULL;
|
||||
|
||||
-- Literal ctx.GetTag("<ref>") reads (EquipmentScriptPaths.ExtractDependencyRefs: ctx\s*\.\s*GetTag\s*\(\s*"([^"]+)"\s*\)).
|
||||
DECLARE @Ref TABLE (ScriptId nvarchar(64), Ref nvarchar(4000) COLLATE Latin1_General_BIN2);
|
||||
WITH occ AS (
|
||||
SELECT s.ScriptId, CHARINDEX(N'ctx.GetTag("', s.Src) AS p
|
||||
FROM @Script s WHERE CHARINDEX(N'ctx.GetTag("', s.Src) > 0
|
||||
UNION ALL
|
||||
SELECT o.ScriptId, CHARINDEX(N'ctx.GetTag("', s.Src, o.p + 1)
|
||||
FROM occ o INNER JOIN @Script s ON s.ScriptId = o.ScriptId
|
||||
WHERE CHARINDEX(N'ctx.GetTag("', s.Src, o.p + 1) > 0
|
||||
)
|
||||
INSERT @Ref (ScriptId, Ref)
|
||||
SELECT o.ScriptId, SUBSTRING(s.Src, o.p + 12, e.q - (o.p + 12))
|
||||
FROM occ o
|
||||
INNER JOIN @Script s ON s.ScriptId = o.ScriptId
|
||||
CROSS APPLY (SELECT CHARINDEX(N'"', s.Src, o.p + 12) AS q) e
|
||||
WHERE e.q > o.p + 12 AND SUBSTRING(s.Src, e.q + 1, 1) = N')'
|
||||
OPTION (MAXRECURSION 0);
|
||||
|
||||
-- RawPath of every raw tag: <folder chain>/<DriverName>/<DeviceName>/<tag-group chain>/<TagName> (RawPathResolver).
|
||||
DECLARE @FolderPath TABLE (RawFolderId nvarchar(64) PRIMARY KEY, Path nvarchar(4000));
|
||||
WITH f AS (
|
||||
SELECT RawFolderId, CAST(Name AS nvarchar(4000)) AS Path, 0 AS d FROM dbo.RawFolder WHERE ParentRawFolderId IS NULL AND RawFolderId IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT c.RawFolderId, CAST(f.Path + N'/' + c.Name AS nvarchar(4000)), f.d + 1
|
||||
FROM dbo.RawFolder c INNER JOIN f ON c.ParentRawFolderId = f.RawFolderId WHERE f.d < 256
|
||||
)
|
||||
INSERT @FolderPath SELECT RawFolderId, MIN(Path) FROM f GROUP BY RawFolderId OPTION (MAXRECURSION 300);
|
||||
|
||||
DECLARE @GroupPath TABLE (TagGroupId nvarchar(64) PRIMARY KEY, Path nvarchar(4000));
|
||||
WITH g AS (
|
||||
SELECT TagGroupId, CAST(Name AS nvarchar(4000)) AS Path, 0 AS d FROM dbo.TagGroup WHERE ParentTagGroupId IS NULL AND TagGroupId IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT c.TagGroupId, CAST(g.Path + N'/' + c.Name AS nvarchar(4000)), g.d + 1
|
||||
FROM dbo.TagGroup c INNER JOIN g ON c.ParentTagGroupId = g.TagGroupId WHERE g.d < 256
|
||||
)
|
||||
INSERT @GroupPath SELECT TagGroupId, MIN(Path) FROM g GROUP BY TagGroupId OPTION (MAXRECURSION 300);
|
||||
|
||||
DECLARE @RawTag TABLE (
|
||||
TagId nvarchar(64), Name nvarchar(128),
|
||||
DeviceId nvarchar(64), DriverInstanceId nvarchar(64),
|
||||
DriverName nvarchar(128), DriverType nvarchar(32) COLLATE Latin1_General_BIN2,
|
||||
AccessLevel nvarchar(16), DataType nvarchar(32), TagConfig nvarchar(max), RawPath nvarchar(4000) COLLATE Latin1_General_BIN2);
|
||||
INSERT @RawTag
|
||||
SELECT t.TagId, t.Name, t.DeviceId, di.DriverInstanceId, di.Name, di.DriverType, t.AccessLevel, t.DataType, t.TagConfig,
|
||||
CASE WHEN di.RawFolderId IS NOT NULL AND fp.Path IS NULL THEN NULL -- unresolvable folder: no RawPath
|
||||
WHEN t.TagGroupId IS NOT NULL AND gp.Path IS NULL THEN NULL -- unresolvable group: no RawPath
|
||||
ELSE ISNULL(fp.Path + N'/', N'') + di.Name + N'/' + dv.Name + N'/' + ISNULL(gp.Path + N'/', N'') + t.Name END
|
||||
FROM dbo.Tag t
|
||||
INNER JOIN dbo.Device dv ON dv.DeviceId = t.DeviceId
|
||||
INNER JOIN dbo.DriverInstance di ON di.DriverInstanceId = dv.DriverInstanceId
|
||||
LEFT JOIN @FolderPath fp ON fp.RawFolderId = di.RawFolderId
|
||||
LEFT JOIN @GroupPath gp ON gp.TagGroupId = t.TagGroupId;
|
||||
|
||||
-- Calculation tags: RawPath + scriptId (TagConfig.scriptId, string) + trigger fields.
|
||||
DECLARE @Calc TABLE (TagId nvarchar(64), RawPath nvarchar(4000) COLLATE Latin1_General_BIN2, ScriptId nvarchar(4000),
|
||||
ChangeTriggered bit, TimerIntervalMs bigint NULL);
|
||||
INSERT @Calc
|
||||
SELECT r.TagId, r.RawPath,
|
||||
(SELECT TOP 1 j.[value] FROM OPENJSON(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END) j WHERE j.[key] COLLATE Latin1_General_BIN2 = N'scriptId' AND j.[type] = 1),
|
||||
ISNULL((SELECT TOP 1 CASE j.[value] WHEN N'false' THEN 0 ELSE 1 END FROM OPENJSON(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END) j
|
||||
WHERE j.[key] COLLATE Latin1_General_BIN2 = N'changeTriggered' AND j.[type] = 3), 1),
|
||||
(SELECT TOP 1 TRY_CAST(j.[value] AS bigint) FROM OPENJSON(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END) j
|
||||
WHERE j.[key] COLLATE Latin1_General_BIN2 = N'timerIntervalMs' AND j.[type] = 2)
|
||||
FROM @RawTag r WHERE r.DriverType = N'Calculation';
|
||||
|
||||
-- UNS reference effective names -> backing RawPath (EquipmentReferenceMap: DisplayNameOverride else raw tag Name).
|
||||
DECLARE @EquipRef TABLE (EquipmentId nvarchar(64) COLLATE Latin1_General_BIN2, EffName nvarchar(128) COLLATE Latin1_General_BIN2, RawPath nvarchar(4000) COLLATE Latin1_General_BIN2);
|
||||
INSERT @EquipRef
|
||||
SELECT u.EquipmentId, CASE WHEN ISNULL(u.DisplayNameOverride, N'') = N'' THEN r.Name ELSE u.DisplayNameOverride END, r.RawPath
|
||||
FROM dbo.UnsTagReference u INNER JOIN @RawTag r ON r.TagId = u.TagId;
|
||||
|
||||
-- Enabled VirtualTags and their mux key {EquipmentId}/{Name}.
|
||||
DECLARE @VT TABLE (VirtualTagId nvarchar(64), EquipmentId nvarchar(64) COLLATE Latin1_General_BIN2, Name nvarchar(128) COLLATE Latin1_General_BIN2,
|
||||
VtKey nvarchar(300) COLLATE Latin1_General_BIN2, ScriptId nvarchar(64),
|
||||
ChangeTriggered bit, TimerIntervalMs int NULL, Enabled bit);
|
||||
INSERT @VT SELECT v.VirtualTagId, v.EquipmentId, v.Name, v.EquipmentId + N'/' + v.Name, v.ScriptId, v.ChangeTriggered, v.TimerIntervalMs, v.Enabled
|
||||
FROM dbo.VirtualTag v;
|
||||
|
||||
-- Which entity uses which script (for reporting).
|
||||
DECLARE @ScriptUse TABLE (ScriptId nvarchar(64), UsedByKind nvarchar(20), UsedById nvarchar(64), UsedByEnabled bit, EquipmentId nvarchar(64) NULL);
|
||||
INSERT @ScriptUse SELECT ScriptId, N'VirtualTag', VirtualTagId, Enabled, EquipmentId FROM @VT;
|
||||
INSERT @ScriptUse SELECT a.PredicateScriptId, N'ScriptedAlarm', a.ScriptedAlarmId, a.Enabled, a.EquipmentId FROM dbo.ScriptedAlarm a;
|
||||
INSERT @ScriptUse SELECT c.ScriptId, N'CalculationTag', c.TagId, 1, NULL FROM @Calc c WHERE c.ScriptId IS NOT NULL;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 0 — environment
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @lastMig nvarchar(150) = (SELECT TOP 1 MigrationId FROM dbo.__EFMigrationsHistory ORDER BY MigrationId DESC);
|
||||
DECLARE @compat int = (SELECT compatibility_level FROM sys.databases WHERE database_id = DB_ID());
|
||||
DECLARE @snap nvarchar(60) = (SELECT snapshot_isolation_state_desc FROM sys.databases WHERE database_id = DB_ID());
|
||||
SELECT N'0' AS check_id, N'Environment' AS title,
|
||||
CASE WHEN @lastMig = N'20260731162342_AddApiKeyTable' AND @compat >= 130 THEN N'ok' ELSE N'attention' END AS status,
|
||||
1 AS row_count, N'NO' AS blocking,
|
||||
N'expects last migration 20260731162342_AddApiKeyTable (b8b4c18f) and compat >= 130' AS source;
|
||||
SELECT DB_NAME() AS database_name, @@SERVERNAME AS server_name, CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(40)) AS sql_version,
|
||||
@compat AS compatibility_level, @lastMig AS last_applied_migration, @snap AS snapshot_isolation_state,
|
||||
N'master needs migration 20261022102459_EnableSnapshotIsolation (ALLOW_SNAPSHOT_ISOLATION ON) before the first deploy' AS note;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 1 — #783 VirtualTagNoEffectiveTrigger (BLOCKING)
|
||||
master: DraftValidator.ValidateVirtualTagTriggers (d9b95dcf). Enabled VT, TimerIntervalMs null/<=0, and either
|
||||
ChangeTriggered = 0, or ChangeTriggered = 1 with a resolvable script that has NO literal ctx.GetTag read.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c1 TABLE (VirtualTagId nvarchar(64), EquipmentId nvarchar(64), Name nvarchar(128), ScriptId nvarchar(64), ChangeTriggered bit, TimerIntervalMs int NULL, literal_reads int, reason nvarchar(200));
|
||||
INSERT @c1
|
||||
SELECT v.VirtualTagId, v.EquipmentId, v.Name, v.ScriptId, v.ChangeTriggered, v.TimerIntervalMs,
|
||||
(SELECT COUNT(*) FROM @Ref r WHERE r.ScriptId = v.ScriptId),
|
||||
CASE WHEN v.ChangeTriggered = 0 THEN N'ChangeTriggered=0 and no TimerIntervalMs'
|
||||
ELSE N'change-triggered but script has no literal ctx.GetTag read, and no TimerIntervalMs' END
|
||||
FROM @VT v
|
||||
WHERE v.Enabled = 1 AND ISNULL(v.TimerIntervalMs, 0) <= 0
|
||||
AND (v.ChangeTriggered = 0
|
||||
OR (EXISTS (SELECT 1 FROM @Script s WHERE s.ScriptId = v.ScriptId) AND NOT EXISTS (SELECT 1 FROM @Ref r WHERE r.ScriptId = v.ScriptId)));
|
||||
SELECT N'1' AS check_id, N'#783 VirtualTagNoEffectiveTrigger' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DraftValidator.ValidateVirtualTagTriggers; commit d9b95dcf' AS source FROM @c1;
|
||||
SELECT * FROM @c1 ORDER BY EquipmentId, Name;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 2 — CalculationNoEffectiveTrigger (BLOCKING)
|
||||
master: DraftValidator.ValidateCalculationTags rule 3 (4d3fc9c8, E4/R07:U5). Calc tag with no timerIntervalMs > 0
|
||||
and either changeTriggered=false, or change-triggered with a resolvable script that has no literal ctx.GetTag read.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c2 TABLE (TagId nvarchar(64), RawPath nvarchar(4000), ScriptId nvarchar(4000), changeTriggered bit, timerIntervalMs bigint NULL, reason nvarchar(200));
|
||||
INSERT @c2
|
||||
SELECT c.TagId, c.RawPath, c.ScriptId, c.ChangeTriggered, c.TimerIntervalMs,
|
||||
CASE WHEN c.ChangeTriggered = 0 THEN N'changeTriggered=false and no timerIntervalMs'
|
||||
ELSE N'change-triggered but script has no literal ctx.GetTag read, and no timerIntervalMs' END
|
||||
FROM @Calc c
|
||||
WHERE ISNULL(c.TimerIntervalMs, 0) <= 0
|
||||
AND (c.ChangeTriggered = 0
|
||||
OR (EXISTS (SELECT 1 FROM @Script s WHERE s.ScriptId = c.ScriptId) AND NOT EXISTS (SELECT 1 FROM @Ref r WHERE r.ScriptId = c.ScriptId)));
|
||||
SELECT N'2' AS check_id, N'CalculationNoEffectiveTrigger' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DraftValidator.ValidateCalculationTags (rule 3); commit 4d3fc9c8' AS source FROM @c2;
|
||||
SELECT * FROM @c2 ORDER BY RawPath;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 3 — #791 scripts that read a VirtualTag (BEHAVIOUR CHANGE, not blocking)
|
||||
master routes VirtualTag outputs through the dependency mux (000d7f55), so a ctx.GetTag("<EquipmentId>/<VtName>")
|
||||
read now RESOLVES and starts producing values / firing where it produced none before. A bare VT name (no '/')
|
||||
never resolved and still does not; it is listed so it can be fixed. {{equip}}/<Name> resolves references only —
|
||||
naming a VT there is EquipReferenceUnresolved (pre-existing error; master's message now hints the key form).
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c3 TABLE (ScriptId nvarchar(64), ScriptName nvarchar(128), used_by nvarchar(max), read_literal nvarchar(4000), match_kind nvarchar(80), target_vt nvarchar(64), target_enabled bit);
|
||||
INSERT @c3
|
||||
SELECT s.ScriptId, s.Name,
|
||||
STUFF((SELECT N', ' + u.UsedByKind + N':' + u.UsedById + CASE WHEN u.UsedByEnabled = 0 THEN N'(disabled)' ELSE N'' END
|
||||
FROM @ScriptUse u WHERE u.ScriptId = s.ScriptId FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N''),
|
||||
r.Ref,
|
||||
CASE WHEN r.Ref = v.VtKey THEN N'VT key {EquipmentId}/{Name}: now resolves (#791)'
|
||||
ELSE N'bare VT name: never resolved, still does not' END,
|
||||
v.VirtualTagId, v.Enabled
|
||||
FROM @Ref r
|
||||
INNER JOIN @Script s ON s.ScriptId = r.ScriptId
|
||||
INNER JOIN @VT v ON r.Ref = v.VtKey OR (r.Ref = v.Name AND CHARINDEX(N'/', r.Ref) = 0);
|
||||
SELECT N'3' AS check_id, N'#791 scripts reading a VirtualTag (VT->VT now works)' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'NO' AS blocking, N'VirtualTag outputs via dependency mux; commit 000d7f55' AS source FROM @c3;
|
||||
SELECT * FROM @c3 ORDER BY ScriptId, read_literal;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 4 — #791 VirtualTagDependencyCycle (BLOCKING) — APPROXIMATE
|
||||
master: DraftValidator.ValidateVirtualTagDependencyCycles (000d7f55). Nodes = enabled VT keys + every Calculation
|
||||
tag RawPath; edges = literal ctx.GetTag reads that are themselves nodes (VT reads after {{equip}}/<RefName>
|
||||
substitution through the equipment's references). Cycles made only of Calculation tags are the pre-existing
|
||||
CalculationDependencyCycle rule and are not listed. Bounded search (25 hops).
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @Node TABLE (NodeKey nvarchar(4000) COLLATE Latin1_General_BIN2, IsVt bit);
|
||||
INSERT @Node SELECT VtKey, 1 FROM @VT WHERE Enabled = 1;
|
||||
INSERT @Node SELECT RawPath, 0 FROM @Calc WHERE RawPath IS NOT NULL AND RawPath NOT IN (SELECT NodeKey FROM @Node);
|
||||
|
||||
DECLARE @Edge TABLE (FromKey nvarchar(4000) COLLATE Latin1_General_BIN2, ToKey nvarchar(4000) COLLATE Latin1_General_BIN2);
|
||||
-- VT edges ({{equip}}/<RefName> substituted to the backing RawPath when the equipment has that reference).
|
||||
INSERT @Edge
|
||||
SELECT DISTINCT v.VtKey, COALESCE(er.RawPath, r.Ref)
|
||||
FROM @VT v
|
||||
INNER JOIN @Ref r ON r.ScriptId = v.ScriptId
|
||||
LEFT JOIN @EquipRef er ON er.EquipmentId = v.EquipmentId AND LEFT(r.Ref, 10) = N'{{equip}}/' AND er.EffName = SUBSTRING(r.Ref, 11, 4000)
|
||||
WHERE v.Enabled = 1 AND COALESCE(er.RawPath, r.Ref) IN (SELECT NodeKey FROM @Node);
|
||||
-- Calculation edges (no {{equip}} substitution for calc scripts).
|
||||
INSERT @Edge
|
||||
SELECT DISTINCT c.RawPath, r.Ref
|
||||
FROM @Calc c INNER JOIN @Ref r ON r.ScriptId = c.ScriptId
|
||||
WHERE c.RawPath IS NOT NULL AND r.Ref IN (SELECT NodeKey FROM @Node);
|
||||
|
||||
DECLARE @c4 TABLE (start_vt_key nvarchar(4000), cycle_path nvarchar(max), hops int);
|
||||
WITH walk AS (
|
||||
SELECT e.FromKey AS StartKey, e.ToKey AS CurKey, CAST(e.FromKey + N' -> ' + e.ToKey AS nvarchar(max)) AS Path,
|
||||
CAST(N'|' + e.FromKey + N'|' AS nvarchar(max)) AS Seen, 1 AS hops
|
||||
FROM @Edge e INNER JOIN @Node n ON n.NodeKey = e.FromKey AND n.IsVt = 1
|
||||
UNION ALL
|
||||
SELECT w.StartKey, e.ToKey, w.Path + N' -> ' + e.ToKey, w.Seen + w.CurKey + N'|', w.hops + 1
|
||||
FROM walk w INNER JOIN @Edge e ON e.FromKey = w.CurKey
|
||||
WHERE w.hops < 25 AND w.CurKey <> w.StartKey AND CHARINDEX(N'|' + w.CurKey + N'|', w.Seen) = 0
|
||||
)
|
||||
INSERT @c4 SELECT StartKey, Path, hops FROM walk WHERE CurKey = StartKey OPTION (MAXRECURSION 30);
|
||||
SELECT N'4' AS check_id, N'#791 VirtualTagDependencyCycle (approximate)' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DraftValidator.ValidateVirtualTagDependencyCycles; commit 000d7f55' AS source FROM @c4;
|
||||
SELECT * FROM @c4 ORDER BY start_vt_key;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 5 — #792 + E2 newly forbidden script APIs (RUNTIME compile failure, not a deploy refusal)
|
||||
master: Core.Scripting/ForbiddenTypeAnalyzer.cs (0e96dd08 #792; 2e916031 E2). ScriptEvaluator throws
|
||||
ScriptSandboxViolationException at compile, so the VT / Calculation tag / scripted alarm never evaluates (and the
|
||||
/scripts editor shows the diagnostic). Text search over the raw source — case-sensitive, may over-report
|
||||
(e.g. a word in a comment); patterns flagged 'review' are known to collide with legitimate APIs.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @Pat TABLE (pattern nvarchar(100) COLLATE Latin1_General_BIN2, denied nvarchar(200), source nvarchar(40), certainty nvarchar(10));
|
||||
INSERT @Pat VALUES
|
||||
(N'%AppContext%', N'System.AppContext', N'#792 0e96dd08', N'likely'),
|
||||
(N'%Console.%', N'System.Console', N'#792 0e96dd08', N'likely'),
|
||||
(N'%GCSettings%', N'System.Runtime.GCSettings', N'#792 0e96dd08', N'likely'),
|
||||
(N'%ProfileOptimization%', N'System.Runtime.ProfileOptimization', N'#792 0e96dd08', N'likely'),
|
||||
(N'%EncodingProvider%', N'System.Text.(CodePages)EncodingProvider', N'#792 0e96dd08', N'likely'),
|
||||
(N'%TypeDescriptor%', N'System.ComponentModel.TypeDescriptor', N'#792 0e96dd08', N'likely'),
|
||||
(N'%CryptoConfig%', N'System.Security.Cryptography.CryptoConfig', N'#792 0e96dd08', N'likely'),
|
||||
(N'%CurrentCulture%', N'CultureInfo.CurrentCulture / DefaultThreadCurrentCulture', N'#792 0e96dd08', N'likely'),
|
||||
(N'%CurrentUICulture%', N'CultureInfo.CurrentUICulture / DefaultThreadCurrentUICulture', N'#792 0e96dd08', N'likely'),
|
||||
(N'%ClearCachedData%', N'TimeZoneInfo.ClearCachedData', N'#792 0e96dd08', N'likely'),
|
||||
(N'%RuntimeHelpers%', N'System.Runtime.CompilerServices.RuntimeHelpers', N'E2 2e916031', N'likely'),
|
||||
(N'%System.Threading%', N'System.Threading namespace (all but CancellationToken/Interlocked/Volatile)', N'E2 2e916031', N'likely'),
|
||||
(N'%SemaphoreSlim%', N'System.Threading.SemaphoreSlim', N'E2 2e916031', N'likely'),
|
||||
(N'%Monitor.%', N'System.Threading.Monitor', N'E2 2e916031', N'review'),
|
||||
(N'%SpinWait%', N'System.Threading.SpinWait', N'E2 2e916031', N'likely'),
|
||||
(N'%SpinLock%', N'System.Threading.SpinLock', N'E2 2e916031', N'likely'),
|
||||
(N'%ResetEvent%', N'System.Threading.Manual/AutoResetEvent(Slim)', N'E2 2e916031', N'likely'),
|
||||
(N'%Mutex%', N'System.Threading.Mutex', N'E2 2e916031', N'likely'),
|
||||
(N'%WaitHandle%', N'System.Threading.(Event)WaitHandle', N'E2 2e916031', N'likely'),
|
||||
(N'%CancellationTokenSource%', N'System.Threading.CancellationTokenSource', N'E2 2e916031', N'likely'),
|
||||
(N'%ReaderWriterLock%', N'System.Threading.ReaderWriterLock(Slim)', N'E2 2e916031', N'likely'),
|
||||
(N'%ThreadLocal%', N'System.Threading.ThreadLocal', N'E2 2e916031', N'likely'),
|
||||
(N'%AsyncLocal%', N'System.Threading.AsyncLocal', N'E2 2e916031', N'likely'),
|
||||
(N'%CountdownEvent%', N'System.Threading.CountdownEvent', N'E2 2e916031', N'likely'),
|
||||
(N'%Barrier%', N'System.Threading.Barrier', N'E2 2e916031', N'review'),
|
||||
(N'%Thread.%', N'System.Threading.Thread (already denied pre-E2)', N'E2 2e916031', N'review'),
|
||||
(N'%Type.GetType(%', N'System.Type.GetType (reflection entry point)', N'E2 2e916031', N'likely'),
|
||||
(N'%.InvokeMember(%', N'System.Type.InvokeMember', N'E2 2e916031', N'likely'),
|
||||
(N'%.GetMethod%(%', N'System.Type.GetMethod(s)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetPropert%(%', N'System.Type.GetProperty/GetProperties (JsonElement.GetProperty is FINE)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetField%(%', N'System.Type.GetField(s)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetMember%(%', N'System.Type.GetMember(s)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetConstructor%(%', N'System.Type.GetConstructor(s)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetEvent%(%', N'System.Type.GetEvent(s)', N'E2 2e916031', N'review'),
|
||||
(N'%.GetDefaultMembers(%', N'System.Type.GetDefaultMembers', N'E2 2e916031', N'likely');
|
||||
|
||||
DECLARE @c5 TABLE (ScriptId nvarchar(64), ScriptName nvarchar(128), used_by nvarchar(max), denied_api nvarchar(200), certainty nvarchar(10), source nvarchar(40));
|
||||
INSERT @c5
|
||||
SELECT s.ScriptId, s.Name,
|
||||
STUFF((SELECT N', ' + u.UsedByKind + N':' + u.UsedById + CASE WHEN u.UsedByEnabled = 0 THEN N'(disabled)' ELSE N'' END
|
||||
FROM @ScriptUse u WHERE u.ScriptId = s.ScriptId FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N''),
|
||||
p.denied, p.certainty, p.source
|
||||
FROM dbo.Script s
|
||||
INNER JOIN @Pat p ON s.SourceCode COLLATE Latin1_General_BIN2 LIKE p.pattern
|
||||
WHERE s.ScriptId IS NOT NULL;
|
||||
SELECT N'5' AS check_id, N'#792/E2 newly forbidden script APIs' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'NO (fails at compile/run)' AS blocking, N'ForbiddenTypeAnalyzer; commits 0e96dd08, 2e916031' AS source FROM @c5;
|
||||
SELECT * FROM @c5 ORDER BY certainty, ScriptId, denied_api;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Credential-field walk shared by checks 6-7 (#815/#835/#849/#836).
|
||||
Policy (Core.Abstractions/DriverCredentialPolicy.cs + CredentialReferences.cs), fields matched at ANY depth,
|
||||
key compared case-insensitively, in DriverConfig AND every DeviceConfig of the driver:
|
||||
GalaxyMxGateway: ApiKeySecretRef (literal allowed), FallbackPasswordSecretRef (literal only as dev:...)
|
||||
secret:galaxy/... | secret:otopcua/galaxy/... | env:OTOPCUA_GALAXY_... | env:GALAXY_MXGW_...
|
||||
OpcUaClient: Password, UserCertificatePassword (literal allowed); path field UserCertificatePath (#836)
|
||||
secret:opcuaclient/... | env:OTOPCUA_OPCUACLIENT_...
|
||||
Heidenhain: InspectPassword, DncPassword, DataPassword (literal refused) — type did not exist at b8b4c18f.
|
||||
${secret:name} is the same as secret:name. file: is always refused. A prefix in another casing is refused.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @Field TABLE (DriverType nvarchar(32), FieldName nvarchar(64), LiteralPolicy nvarchar(16));
|
||||
INSERT @Field VALUES
|
||||
(N'GalaxyMxGateway', N'ApiKeySecretRef', N'Allowed'), (N'GalaxyMxGateway', N'FallbackPasswordSecretRef', N'DevPrefixOnly'),
|
||||
(N'OpcUaClient', N'Password', N'Allowed'), (N'OpcUaClient', N'UserCertificatePassword', N'Allowed'),
|
||||
(N'Heidenhain', N'InspectPassword', N'Refused'), (N'Heidenhain', N'DncPassword', N'Refused'), (N'Heidenhain', N'DataPassword', N'Refused');
|
||||
DECLARE @Ns TABLE (DriverType nvarchar(32), Kind nvarchar(10), Prefix nvarchar(64) COLLATE Latin1_General_BIN2);
|
||||
INSERT @Ns VALUES
|
||||
(N'GalaxyMxGateway', N'secret', N'galaxy/'), (N'GalaxyMxGateway', N'secret', N'otopcua/galaxy/'),
|
||||
(N'GalaxyMxGateway', N'env', N'OTOPCUA_GALAXY_'), (N'GalaxyMxGateway', N'env', N'GALAXY_MXGW_'),
|
||||
(N'OpcUaClient', N'secret', N'opcuaclient/'), (N'OpcUaClient', N'env', N'OTOPCUA_OPCUACLIENT_'),
|
||||
(N'Heidenhain', N'secret', N'heidenhain/'), (N'Heidenhain', N'env', N'OTOPCUA_HEIDENHAIN_');
|
||||
|
||||
-- Credential-bearing config blobs: DriverConfig + each DeviceConfig.
|
||||
DECLARE @Blob TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128), DriverType nvarchar(32), BlobKind nvarchar(30), OwnerId nvarchar(64), OwnerName nvarchar(128), Config nvarchar(max));
|
||||
INSERT @Blob
|
||||
SELECT d.DriverInstanceId, d.Name, ft.DriverType, N'DriverConfig', d.DriverInstanceId, d.Name, d.DriverConfig
|
||||
FROM dbo.DriverInstance d INNER JOIN (SELECT DISTINCT DriverType FROM @Field) ft ON ft.DriverType = d.DriverType -- table is case-insensitive like the C# policy
|
||||
UNION ALL
|
||||
SELECT d.DriverInstanceId, d.Name, ft.DriverType, N'DeviceConfig', v.DeviceId, v.Name, v.DeviceConfig
|
||||
FROM dbo.DriverInstance d INNER JOIN (SELECT DISTINCT DriverType FROM @Field) ft ON ft.DriverType = d.DriverType
|
||||
INNER JOIN dbo.Device v ON v.DriverInstanceId = d.DriverInstanceId;
|
||||
|
||||
-- Recursive JSON walk: every property at every depth, with its parent object (for the #836 AuthType switch).
|
||||
DECLARE @Prop TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128), DriverType nvarchar(32), BlobKind nvarchar(30), OwnerId nvarchar(64), OwnerName nvarchar(128),
|
||||
FieldPath nvarchar(4000), PropKey nvarchar(4000), PropValue nvarchar(max) COLLATE Latin1_General_BIN2, PropType tinyint, ParentJson nvarchar(max));
|
||||
WITH j AS (
|
||||
SELECT b.DriverInstanceId, b.DriverName, b.DriverType, b.BlobKind, b.OwnerId, b.OwnerName,
|
||||
CAST(o.[key] AS nvarchar(4000)) AS FieldPath, CAST(o.[key] AS nvarchar(4000)) AS PropKey, o.[value] AS PropValue, CAST(o.[type] AS tinyint) AS PropType,
|
||||
CAST(b.Config AS nvarchar(max)) AS ParentJson, 0 AS depth, CAST(0 AS bit) AS InArray
|
||||
FROM @Blob b CROSS APPLY OPENJSON(CASE WHEN ISJSON(b.Config) = 1 THEN b.Config END) o
|
||||
UNION ALL
|
||||
SELECT j.DriverInstanceId, j.DriverName, j.DriverType, j.BlobKind, j.OwnerId, j.OwnerName,
|
||||
CAST(CASE WHEN j.PropType = 4 THEN j.FieldPath + N'[' + c.[key] + N']' ELSE j.FieldPath + N'.' + c.[key] END AS nvarchar(4000)),
|
||||
CAST(c.[key] AS nvarchar(4000)), c.[value], CAST(c.[type] AS tinyint), CAST(j.PropValue AS nvarchar(max)), j.depth + 1,
|
||||
CAST(CASE WHEN j.PropType = 4 THEN 1 ELSE 0 END AS bit)
|
||||
FROM j CROSS APPLY OPENJSON(j.PropValue) c
|
||||
WHERE j.PropType IN (4, 5) AND j.depth < 32
|
||||
)
|
||||
INSERT @Prop SELECT DriverInstanceId, DriverName, DriverType, BlobKind, OwnerId, OwnerName, FieldPath, PropKey, PropValue, PropType, ParentJson
|
||||
FROM j WHERE InArray = 0 OPTION (MAXRECURSION 40);
|
||||
|
||||
-- Classify every credential field value (CredentialReferences.Parse + IsAcceptable).
|
||||
DECLARE @Cred TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128), DriverType nvarchar(32), BlobKind nvarchar(30), OwnerId nvarchar(64), OwnerName nvarchar(128),
|
||||
FieldPath nvarchar(4000), LiteralPolicy nvarchar(16), Kind nvarchar(80), RefName nvarchar(4000) COLLATE Latin1_General_BIN2, Acceptable bit, Shown nvarchar(4000));
|
||||
INSERT @Cred
|
||||
SELECT p.DriverInstanceId, p.DriverName, p.DriverType, p.BlobKind, p.OwnerId, p.OwnerName, p.FieldPath, f.LiteralPolicy, k.Kind, k.RefName,
|
||||
CASE WHEN k.Kind IN (N'blank', N'secret', N'env') THEN 1
|
||||
WHEN k.Kind = N'literal' AND f.LiteralPolicy = N'Allowed' THEN 1
|
||||
WHEN k.Kind = N'literal' AND f.LiteralPolicy = N'DevPrefixOnly' AND LOWER(LEFT(p.PropValue, 4)) = N'dev:' THEN 1
|
||||
ELSE 0 END,
|
||||
CASE WHEN k.Kind IN (N'literal') THEN N'<literal redacted>' WHEN k.Kind = N'non-string' THEN N'<non-string JSON value>'
|
||||
WHEN LOWER(LEFT(p.PropValue, 4)) = N'dev:' THEN N'dev:<redacted>' ELSE p.PropValue END
|
||||
FROM @Prop p
|
||||
INNER JOIN @Field f ON f.DriverType = p.DriverType AND LOWER(f.FieldName) = LOWER(p.PropKey)
|
||||
CROSS APPLY (
|
||||
SELECT
|
||||
CASE WHEN p.PropType = 0 OR (p.PropType = 1 AND p.PropValue = N'') THEN N'blank'
|
||||
WHEN p.PropType <> 1 THEN N'non-string'
|
||||
WHEN (LOWER(LEFT(p.PropValue, 9)) = N'${secret:' AND LEFT(p.PropValue, 9) <> N'${secret:')
|
||||
OR (LOWER(LEFT(p.PropValue, 7)) = N'secret:' AND LEFT(p.PropValue, 7) <> N'secret:')
|
||||
OR (LOWER(LEFT(p.PropValue, 4)) = N'env:' AND LEFT(p.PropValue, 4) <> N'env:') THEN N'out-of-scope (prefix casing)'
|
||||
WHEN LEFT(p.PropValue, 9) = N'${secret:' AND RIGHT(p.PropValue, 1) <> N'}' THEN N'out-of-scope (malformed ${secret:})'
|
||||
WHEN LEFT(p.PropValue, 9) = N'${secret:' OR LEFT(p.PropValue, 7) = N'secret:' THEN N'secret?'
|
||||
WHEN LEFT(p.PropValue, 4) = N'env:' THEN N'env?'
|
||||
WHEN LOWER(LEFT(p.PropValue, 5)) = N'file:' THEN N'out-of-scope (file:)'
|
||||
ELSE N'literal' END AS RawKind,
|
||||
CASE WHEN LEFT(p.PropValue, 9) = N'${secret:' AND RIGHT(p.PropValue, 1) = N'}' THEN SUBSTRING(p.PropValue, 10, LEN(p.PropValue) - 10)
|
||||
WHEN LEFT(p.PropValue, 7) = N'secret:' THEN SUBSTRING(p.PropValue, 8, 4000)
|
||||
WHEN LEFT(p.PropValue, 4) = N'env:' THEN SUBSTRING(p.PropValue, 5, 4000) END AS Name
|
||||
) rk
|
||||
CROSS APPLY (
|
||||
SELECT
|
||||
CASE WHEN rk.RawKind = N'secret?' THEN
|
||||
CASE WHEN EXISTS (SELECT 1 FROM @Ns n WHERE n.DriverType = p.DriverType AND n.Kind = N'secret'
|
||||
AND LEFT(rk.Name, LEN(n.Prefix)) = n.Prefix
|
||||
AND LEN(rk.Name) - LEN(n.Prefix) BETWEEN 1 AND 200
|
||||
AND SUBSTRING(rk.Name, LEN(n.Prefix) + 1, 4000) NOT LIKE N'%[^-A-Za-z0-9._/]%'
|
||||
AND N'/' + SUBSTRING(rk.Name, LEN(n.Prefix) + 1, 4000) + N'/' NOT LIKE N'%//%'
|
||||
AND N'/' + SUBSTRING(rk.Name, LEN(n.Prefix) + 1, 4000) + N'/' NOT LIKE N'%/./%'
|
||||
AND N'/' + SUBSTRING(rk.Name, LEN(n.Prefix) + 1, 4000) + N'/' NOT LIKE N'%/../%')
|
||||
THEN N'secret' ELSE N'out-of-scope (secret: outside namespace)' END
|
||||
WHEN rk.RawKind = N'env?' THEN
|
||||
CASE WHEN EXISTS (SELECT 1 FROM @Ns n WHERE n.DriverType = p.DriverType AND n.Kind = N'env'
|
||||
AND LEFT(rk.Name, LEN(n.Prefix)) = n.Prefix
|
||||
AND LEN(rk.Name) - LEN(n.Prefix) BETWEEN 1 AND 100
|
||||
AND SUBSTRING(rk.Name, LEN(n.Prefix) + 1, 4000) NOT LIKE N'%[^A-Z0-9_]%')
|
||||
THEN N'env' ELSE N'out-of-scope (env: outside prefix)' END
|
||||
ELSE rk.RawKind END AS Kind,
|
||||
rk.Name AS RefName
|
||||
) k;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 6 — #815 CredentialReferenceOutOfScope (+ #836 UserCertificatePath) (BLOCKING)
|
||||
master: Configuration/Validation/CredentialReferenceDeployChecks.cs (a224fd63 #815; 63331b04 #836; 3dbfe6ec #850).
|
||||
A RUNNING driver keeps resolving an old reference warn-only (LegacyCredentialReferenceTracker), but the NEXT deploy
|
||||
is refused until the field is migrated. Same rule the AdminUI /raw banner and CredentialReferencePreflightService
|
||||
report (#834). Also lists config blobs that are not valid JSON (the gate reports those too).
|
||||
UserCertificatePath: must be blank or a relative path of [A-Za-z0-9._-] segments inside
|
||||
Drivers:OpcUaClient:UserCertificateDirectory; only checked where AuthType in the SAME object is absent/null/
|
||||
'Certificate'/2 — rows where AuthType sits in the other blob are 'review' (the gate checks the merged config, #850).
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c6 TABLE (DriverType nvarchar(32), DriverName nvarchar(128), DriverInstanceId nvarchar(64), blob nvarchar(30), owner_name nvarchar(128), field_path nvarchar(4000),
|
||||
finding nvarchar(200), value_shown nvarchar(4000), severity nvarchar(10));
|
||||
INSERT @c6
|
||||
SELECT c.DriverType, c.DriverName, c.DriverInstanceId, c.BlobKind, c.OwnerName, c.FieldPath,
|
||||
CASE WHEN c.Kind = N'literal' AND c.LiteralPolicy = N'DevPrefixOnly' THEN N'literal refused: must be secret:/env: in namespace or explicit dev:VALUE'
|
||||
WHEN c.Kind = N'literal' THEN N'literal refused for this field'
|
||||
ELSE c.Kind END,
|
||||
c.Shown, N'blocking'
|
||||
FROM @Cred c WHERE c.Acceptable = 0;
|
||||
INSERT @c6
|
||||
SELECT b.DriverType, b.DriverName, b.DriverInstanceId, b.BlobKind, b.OwnerName, N'(whole blob)', N'config is not valid JSON (credential fields cannot be checked) — verify; comments/trailing commas may be a false positive', NULL, N'blocking'
|
||||
FROM @Blob b WHERE ISJSON(b.Config) = 0 AND LTRIM(RTRIM(ISNULL(b.Config, N''))) <> N'';
|
||||
INSERT @c6
|
||||
SELECT p.DriverType, p.DriverName, p.DriverInstanceId, p.BlobKind, p.OwnerName, p.FieldPath,
|
||||
CASE WHEN p.PropType <> 1 THEN N'#836 UserCertificatePath is not a string'
|
||||
ELSE N'#836 UserCertificatePath must be a relative path inside Drivers:OpcUaClient:UserCertificateDirectory' END,
|
||||
CASE WHEN p.PropType = 1 THEN p.PropValue END,
|
||||
CASE WHEN a.AuthType IS NULL OR a.AuthTypeType = 0 OR (a.AuthTypeType = 1 AND LOWER(a.AuthType) = N'certificate')
|
||||
OR (a.AuthTypeType = 2 AND (TRY_CAST(a.AuthType AS int) IS NULL OR TRY_CAST(a.AuthType AS int) = 2)) OR a.AuthTypeType NOT IN (0, 1, 2)
|
||||
THEN N'blocking' ELSE N'review' END
|
||||
FROM @Prop p
|
||||
OUTER APPLY (SELECT TOP 1 o.[value] AS AuthType, o.[type] AS AuthTypeType FROM OPENJSON(p.ParentJson) o WHERE LOWER(o.[key]) = N'authtype') a
|
||||
WHERE p.DriverType = N'OpcUaClient' AND LOWER(p.PropKey) = N'usercertificatepath'
|
||||
AND p.PropType <> 0 AND NOT (p.PropType = 1 AND p.PropValue = N'')
|
||||
AND (p.PropType <> 1
|
||||
OR LEN(p.PropValue) > 260
|
||||
OR LEFT(p.PropValue, 1) IN (N'/', N'\')
|
||||
OR p.PropValue LIKE N'_:%'
|
||||
OR N'/' + REPLACE(p.PropValue, N'\', N'/') + N'/' LIKE N'%//%'
|
||||
OR N'/' + REPLACE(p.PropValue, N'\', N'/') + N'/' LIKE N'%/./%'
|
||||
OR N'/' + REPLACE(p.PropValue, N'\', N'/') + N'/' LIKE N'%/../%'
|
||||
OR REPLACE(p.PropValue, N'\', N'/') LIKE N'%[^-A-Za-z0-9._/]%');
|
||||
SELECT N'6' AS check_id, N'#815/#836 CredentialReferenceOutOfScope' AS title,
|
||||
CASE WHEN SUM(CASE WHEN severity = N'blocking' THEN 1 ELSE 0 END) > 0 THEN N'attention' WHEN COUNT(*) > 0 THEN N'info' ELSE N'ok' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'CredentialReferenceDeployChecks / DriverCredentialPolicy; commits a224fd63, 63331b04, 3dbfe6ec' AS source FROM @c6;
|
||||
SELECT * FROM @c6 ORDER BY severity, DriverType, DriverName, field_path;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 7 — #835/#849 CredentialReferenceOtherInstance (BLOCKING)
|
||||
master: DriverCredentialPolicy.DescribeOtherInstanceProblems via CredentialReferenceDeployChecks (63331b04 #835,
|
||||
4a6229e4 #849, d68dff09). An in-scope reference whose instance part names ANOTHER driver of the same type:
|
||||
secret:<ns>/<X>/... X equals another driver's id or name (case-insensitive);
|
||||
env:<PREFIX><X>... X equals another driver's id/name in env form (ASCII letters upper-cased, digits kept, every
|
||||
other char '_'), alone or followed by '_'.
|
||||
An id-shaped part (secret DRV-<12 hex>, env DRV_<12 HEX>) matches by id only.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @Drv TABLE (DriverInstanceId nvarchar(64), Name nvarchar(128), DriverType nvarchar(32), IdNorm nvarchar(256) COLLATE Latin1_General_BIN2, NameNorm nvarchar(256) COLLATE Latin1_General_BIN2);
|
||||
INSERT @Drv
|
||||
SELECT d.DriverInstanceId, d.Name, d.DriverType,
|
||||
CASE WHEN d.DriverInstanceId COLLATE Latin1_General_BIN2 LIKE N'%[A-Za-z0-9]%' THEN
|
||||
(SELECT CASE WHEN SUBSTRING(d.DriverInstanceId, n.n, 1) COLLATE Latin1_General_BIN2 LIKE N'[A-Za-z0-9]' THEN UPPER(SUBSTRING(d.DriverInstanceId, n.n, 1)) ELSE N'_' END
|
||||
FROM @Num n WHERE n.n <= DATALENGTH(d.DriverInstanceId) / 2 ORDER BY n.n FOR XML PATH('')) ELSE N'' END,
|
||||
CASE WHEN d.Name COLLATE Latin1_General_BIN2 LIKE N'%[A-Za-z0-9]%' THEN
|
||||
(SELECT CASE WHEN SUBSTRING(d.Name, n.n, 1) COLLATE Latin1_General_BIN2 LIKE N'[A-Za-z0-9]' THEN UPPER(SUBSTRING(d.Name, n.n, 1)) ELSE N'_' END
|
||||
FROM @Num n WHERE n.n <= DATALENGTH(d.Name) / 2 ORDER BY n.n FOR XML PATH('')) ELSE N'' END
|
||||
FROM dbo.DriverInstance d WHERE d.DriverType IN (SELECT DriverType FROM @Field);
|
||||
|
||||
DECLARE @InstRef TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128), DriverType nvarchar(32), BlobKind nvarchar(30), OwnerName nvarchar(128), FieldPath nvarchar(4000),
|
||||
Kind nvarchar(10), Part nvarchar(4000) COLLATE Latin1_General_BIN2, IdShaped bit, Shown nvarchar(4000));
|
||||
INSERT @InstRef
|
||||
SELECT c.DriverInstanceId, c.DriverName, c.DriverType, c.BlobKind, c.OwnerName, c.FieldPath, c.Kind, x.Part,
|
||||
CASE WHEN c.Kind = N'secret' THEN CASE WHEN LEN(x.Part) = 16 AND UPPER(LEFT(x.Part, 4)) = N'DRV-' AND SUBSTRING(x.Part, 5, 12) NOT LIKE N'%[^0-9A-Fa-f]%' THEN 1 ELSE 0 END
|
||||
ELSE CASE WHEN LEFT(x.Part, 4) = N'DRV_' AND LEN(x.Part) >= 16 AND SUBSTRING(x.Part, 5, 12) NOT LIKE N'%[^0-9A-F]%'
|
||||
AND (LEN(x.Part) = 16 OR SUBSTRING(x.Part, 17, 1) = N'_') THEN 1 ELSE 0 END END,
|
||||
c.Shown
|
||||
FROM @Cred c
|
||||
CROSS APPLY (SELECT TOP 1 n.Prefix FROM @Ns n WHERE n.DriverType = c.DriverType AND n.Kind = c.Kind AND LEFT(c.RefName, LEN(n.Prefix)) = n.Prefix ORDER BY LEN(n.Prefix) DESC) ns
|
||||
CROSS APPLY (SELECT SUBSTRING(c.RefName, LEN(ns.Prefix) + 1, 4000) AS Rest) r
|
||||
CROSS APPLY (SELECT CASE WHEN c.Kind = N'secret' AND CHARINDEX(N'/', r.Rest) > 0 THEN LEFT(r.Rest, CHARINDEX(N'/', r.Rest) - 1) ELSE r.Rest END AS Part) x
|
||||
WHERE c.Kind IN (N'secret', N'env');
|
||||
|
||||
DECLARE @c7 TABLE (DriverType nvarchar(32), DriverName nvarchar(128), DriverInstanceId nvarchar(64), blob nvarchar(30), owner_name nvarchar(128), field_path nvarchar(4000),
|
||||
reference nvarchar(4000), names_other_driver nvarchar(128), other_driver_id nvarchar(64), matched_on nvarchar(10));
|
||||
INSERT @c7
|
||||
SELECT i.DriverType, i.DriverName, i.DriverInstanceId, i.BlobKind, i.OwnerName, i.FieldPath, i.Shown, o.Name, o.DriverInstanceId,
|
||||
CASE WHEN (i.Kind = N'secret' AND LOWER(i.Part) = LOWER(o.DriverInstanceId) COLLATE Latin1_General_BIN2)
|
||||
OR (i.Kind = N'env' AND o.IdNorm <> N'' AND (i.Part = o.IdNorm OR LEFT(i.Part, LEN(o.IdNorm) + 1) = o.IdNorm + N'_')) THEN N'id' ELSE N'name' END
|
||||
FROM @InstRef i
|
||||
INNER JOIN @Drv o ON LOWER(o.DriverType) = LOWER(i.DriverType) AND o.DriverInstanceId <> i.DriverInstanceId
|
||||
WHERE (i.Kind = N'secret' AND (LOWER(i.Part) = LOWER(o.DriverInstanceId) COLLATE Latin1_General_BIN2 OR (i.IdShaped = 0 AND LOWER(i.Part) = LOWER(o.Name) COLLATE Latin1_General_BIN2)))
|
||||
OR (i.Kind = N'env' AND (
|
||||
(o.IdNorm <> N'' AND (i.Part = o.IdNorm OR LEFT(i.Part, LEN(o.IdNorm) + 1) = o.IdNorm + N'_'))
|
||||
OR (i.IdShaped = 0 AND o.NameNorm <> N'' AND (i.Part = o.NameNorm OR LEFT(i.Part, LEN(o.NameNorm) + 1) = o.NameNorm + N'_'))));
|
||||
SELECT N'7' AS check_id, N'#835/#849 CredentialReferenceOtherInstance' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DriverCredentialPolicy.DescribeOtherInstanceProblems; commits 63331b04, 4a6229e4, d68dff09' AS source FROM @c7;
|
||||
SELECT * FROM @c7 ORDER BY DriverType, DriverName, field_path;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 8 — #848/#849 reserved / claimed driver names (AUTHORING, not a deploy refusal)
|
||||
master: DriverCredentialPolicy.IsReservedInstanceName + DriverConfigSaveGate.DescribeNameChangeProblemsAsync (d08e59ee,
|
||||
d68dff09). A credential-bearing driver named 'drv' / 'DRV-<12 hex>...' (env form DRV or DRV_<12 HEX>) cannot be
|
||||
created or renamed to that name; listed so a later rename/save surprise is expected.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c8 TABLE (DriverType nvarchar(32), DriverName nvarchar(128), DriverInstanceId nvarchar(64), env_form nvarchar(256), finding nvarchar(200));
|
||||
INSERT @c8
|
||||
SELECT d.DriverType, d.Name, d.DriverInstanceId, d.NameNorm, N'name is reserved (collides with the DRV-<id> credential convention)'
|
||||
FROM @Drv d
|
||||
WHERE d.NameNorm = N'DRV'
|
||||
OR (LEFT(d.NameNorm, 4) = N'DRV_' AND LEN(d.NameNorm) >= 16 AND SUBSTRING(d.NameNorm, 5, 12) NOT LIKE N'%[^0-9A-F]%'
|
||||
AND (LEN(d.NameNorm) = 16 OR SUBSTRING(d.NameNorm, 17, 1) = N'_'));
|
||||
SELECT N'8' AS check_id, N'#848/#849 reserved driver names (authoring only)' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'NO (save/rename refused)' AS blocking, N'DriverCredentialPolicy.IsReservedInstanceName; commits d08e59ee, d68dff09' AS source FROM @c8;
|
||||
SELECT * FROM @c8;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 9 — MTConnectMultipleDevices (BLOCKING)
|
||||
master: DraftValidator.ValidateMTConnectSingleDevice (dcc827ca, #628). More than one /raw Device under one
|
||||
MTConnect driver instance.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c9 TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128), device_count int, devices nvarchar(max));
|
||||
INSERT @c9
|
||||
SELECT d.DriverInstanceId, d.Name, COUNT(*),
|
||||
STUFF((SELECT N', ' + v2.Name FROM dbo.Device v2 WHERE v2.DriverInstanceId = d.DriverInstanceId FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N'')
|
||||
FROM dbo.DriverInstance d INNER JOIN dbo.Device v ON v.DriverInstanceId = d.DriverInstanceId
|
||||
WHERE d.DriverType COLLATE Latin1_General_BIN2 = N'MTConnect'
|
||||
GROUP BY d.DriverInstanceId, d.Name HAVING COUNT(*) > 1;
|
||||
SELECT N'9' AS check_id, N'MTConnectMultipleDevices' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DraftValidator.ValidateMTConnectSingleDevice; commit dcc827ca (#628)' AS source FROM @c9;
|
||||
SELECT * FROM @c9;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 10 — #781 Sql tag gates
|
||||
10a SqlQueryNotDeclared (BLOCKING, approximate): a Query-model tag ("model":"Query") whose "query" is not a key of
|
||||
the driver's merged "queries" map (DriverConfig, shallow-overridden by the DeviceConfig when the driver has
|
||||
exactly one Device — DriverDeviceConfigMerger). master: SqlTagDeployChecks.UndeclaredQueryErrors (241318e7).
|
||||
10b Sql tags carrying a "write" block (REVIEW): the block is now parsed (f5c30ed0); an invalid block is
|
||||
SqlWriteBlockInvalid (BLOCKING, and the driver would skip the whole tag), Upsert/Procedure on an Odbc provider
|
||||
is SqlWriteModeUnsupported (BLOCKING). master: SqlTagDeployChecks.WriteBlockErrors.
|
||||
10c Writable mismatch (WARNING only, logged at deploy): Tag.AccessLevel=ReadWrite vs TagConfig "writable": true.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c10 TABLE (sub nvarchar(4), TagId nvarchar(64), RawPath nvarchar(4000), DriverName nvarchar(128), finding nvarchar(300), severity nvarchar(10));
|
||||
WITH sqlTag AS (
|
||||
SELECT r.*, (SELECT COUNT(*) FROM dbo.Device v WHERE v.DriverInstanceId = r.DriverInstanceId) AS DeviceCount
|
||||
FROM @RawTag r WHERE r.DriverType = N'Sql' AND ISJSON(r.TagConfig) = 1
|
||||
)
|
||||
INSERT @c10
|
||||
SELECT N'10a', t.TagId, t.RawPath, t.DriverName, N'query ''' + q.QueryName + N''' not declared under the driver''s queries', N'blocking'
|
||||
FROM sqlTag t
|
||||
CROSS APPLY (SELECT JSON_VALUE(t.TagConfig, '$.query') AS QueryName, JSON_VALUE(t.TagConfig, '$.model') AS Model) q
|
||||
OUTER APPLY (SELECT TOP 1 v.DeviceConfig FROM dbo.Device v WHERE v.DriverInstanceId = t.DriverInstanceId AND t.DeviceCount = 1
|
||||
AND ISJSON(v.DeviceConfig) = 1 AND EXISTS (SELECT 1 FROM OPENJSON(v.DeviceConfig) o WHERE o.[key] COLLATE Latin1_General_BIN2 = N'queries')) dev
|
||||
CROSS APPLY (SELECT ISNULL(dev.DeviceConfig, (SELECT di.DriverConfig FROM dbo.DriverInstance di WHERE di.DriverInstanceId = t.DriverInstanceId)) AS Cfg) m
|
||||
WHERE LOWER(q.Model) = N'query' AND q.QueryName IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM OPENJSON(CASE WHEN ISJSON(m.Cfg) = 1 THEN m.Cfg END) top1
|
||||
CROSS APPLY OPENJSON(CASE WHEN top1.[type] = 5 THEN top1.[value] END) qk
|
||||
WHERE LOWER(top1.[key]) = N'queries' AND qk.[key] COLLATE Latin1_General_BIN2 = q.QueryName COLLATE Latin1_General_BIN2);
|
||||
INSERT @c10
|
||||
SELECT N'10b', r.TagId, r.RawPath, r.DriverName, N'TagConfig has a "write" block — verify it parses (mode/table/keys) and the provider serves the mode', N'review'
|
||||
FROM @RawTag r WHERE r.DriverType = N'Sql' AND ISJSON(r.TagConfig) = 1
|
||||
AND EXISTS (SELECT 1 FROM OPENJSON(r.TagConfig) o WHERE o.[key] COLLATE Latin1_General_BIN2 = N'write');
|
||||
INSERT @c10
|
||||
SELECT N'10c', r.TagId, r.RawPath, r.DriverName,
|
||||
CASE WHEN r.AccessLevel = N'ReadWrite' THEN N'AccessLevel ReadWrite but TagConfig lacks "writable": true — writes refused'
|
||||
ELSE N'TagConfig "writable": true but AccessLevel is not ReadWrite — node read-only' END, N'warning'
|
||||
FROM @RawTag r
|
||||
CROSS APPLY (SELECT CASE WHEN EXISTS (SELECT 1 FROM OPENJSON(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END) o
|
||||
WHERE o.[key] COLLATE Latin1_General_BIN2 = N'writable' AND o.[type] = 3 AND o.[value] = N'true') THEN 1 ELSE 0 END AS BlobWritable) w
|
||||
WHERE r.DriverType = N'Sql' AND (CASE WHEN r.AccessLevel = N'ReadWrite' THEN 1 ELSE 0 END) <> w.BlobWritable;
|
||||
SELECT N'10' AS check_id, N'#781 Sql tag gates' AS title,
|
||||
CASE WHEN SUM(CASE WHEN severity IN (N'blocking', N'review') THEN 1 ELSE 0 END) > 0 THEN N'attention' WHEN COUNT(*) > 0 THEN N'info' ELSE N'ok' END AS status,
|
||||
COUNT(*) AS row_count, N'10a/10b YES, 10c NO' AS blocking, N'SqlTagDeployChecks; commits 241318e7, f5c30ed0' AS source FROM @c10;
|
||||
SELECT * FROM @c10 ORDER BY sub, RawPath;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 11 — #803 Heidenhain gates (BLOCKING) — sanity only: the Heidenhain driver type did not exist at b8b4c18f,
|
||||
so any row here was written by something other than the b8b4c18f AdminUI.
|
||||
master: HeidenhainDeployChecks (2b45694f).
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c11 TABLE (DriverInstanceId nvarchar(64), DriverName nvarchar(128));
|
||||
INSERT @c11 SELECT DriverInstanceId, Name FROM dbo.DriverInstance WHERE LOWER(DriverType) = N'heidenhain';
|
||||
SELECT N'11' AS check_id, N'#803 Heidenhain drivers present (expected none)' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'HeidenhainDeployChecks; commit 2b45694f' AS source FROM @c11;
|
||||
SELECT * FROM @c11;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 12 — UNS effective-name collisions (pre-existing rule, unchanged; re-checked because the whole DB is the
|
||||
draft). master: DraftValidator.ValidateUnsEffectiveLeafUniqueness. References (DisplayNameOverride else raw tag
|
||||
Name), VirtualTags and ScriptedAlarms share {EquipmentId}/{EffectiveName}; compared ordinally.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @Leaf TABLE (EquipmentId nvarchar(64) COLLATE Latin1_General_BIN2, EffName nvarchar(128) COLLATE Latin1_General_BIN2, Src nvarchar(200));
|
||||
INSERT @Leaf SELECT u.EquipmentId, ISNULL(u.DisplayNameOverride, t.Name), N'UnsTagReference ' + u.UnsTagReferenceId
|
||||
FROM dbo.UnsTagReference u LEFT JOIN dbo.Tag t ON t.TagId = u.TagId WHERE ISNULL(u.DisplayNameOverride, t.Name) <> N'';
|
||||
INSERT @Leaf SELECT EquipmentId, Name, N'VirtualTag ' + VirtualTagId FROM dbo.VirtualTag WHERE Name <> N'';
|
||||
INSERT @Leaf SELECT EquipmentId, Name, N'ScriptedAlarm ' + ScriptedAlarmId FROM dbo.ScriptedAlarm WHERE Name <> N'';
|
||||
DECLARE @c12 TABLE (EquipmentId nvarchar(64), EffectiveName nvarchar(128), sources nvarchar(max));
|
||||
INSERT @c12
|
||||
SELECT l.EquipmentId, l.EffName,
|
||||
STUFF((SELECT N', ' + l2.Src FROM @Leaf l2 WHERE l2.EquipmentId = l.EquipmentId AND l2.EffName = l.EffName FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 2, N'')
|
||||
FROM @Leaf l GROUP BY l.EquipmentId, l.EffName HAVING COUNT(*) > 1;
|
||||
SELECT N'12' AS check_id, N'UnsEffectiveName collisions (pre-existing rule)' AS title, CASE WHEN COUNT(*) = 0 THEN N'ok' ELSE N'attention' END AS status,
|
||||
COUNT(*) AS row_count, N'YES' AS blocking, N'DraftValidator.ValidateUnsEffectiveLeafUniqueness (unchanged since b8b4c18f)' AS source FROM @c12;
|
||||
SELECT * FROM @c12 ORDER BY EquipmentId, EffectiveName;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 13 — FOCAS tags whose authored address or value changes under master (the box's one driver)
|
||||
Not deploy refusals: the TagConfig inspector reports 'String' mismatches at deploy as WARNINGS
|
||||
(Deployment:TagConfigValidationMode = Warn, the default) and the driver skips that tag (BadNodeIdUnknown).
|
||||
rename FIXED: ...MaxRpm -> MotorSpeedPct (1dba46bd)
|
||||
rename FIXED:Axes/AA_1, AA_2 -> AA6, AA7 on the Makino (9460da51 — old names were a decode artefact)
|
||||
refused dataType String on a PMC / PARAM: / MACRO: address (cc140593, R07:U8)
|
||||
value FIXED:Axes/... positions scaled by the block envelope (69e0a622: 2801574 -> 280.1574)
|
||||
value servo/spindle Load: real percent, spindle Load Float64 (1dba46bd)
|
||||
value MACRO: decoded at the right offset (665ecd55: was off by 10^n); vacant -> BadNoData,
|
||||
fractional on an integer tag -> BadTypeMismatch
|
||||
value FIXED:Program/...: an unreported field -> BadNoData instead of Good 0 (26b512ad)
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
DECLARE @c13 TABLE (TagId nvarchar(64), RawPath nvarchar(4000), address nvarchar(400), tag_config_dataType nvarchar(40), entity_DataType nvarchar(32), finding nvarchar(300), severity nvarchar(10));
|
||||
INSERT @c13
|
||||
SELECT r.TagId, r.RawPath, a.Addr, a.Dt, r.DataType, f.Finding, f.Severity
|
||||
FROM @RawTag r
|
||||
CROSS APPLY (SELECT LTRIM(RTRIM(JSON_VALUE(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END, '$.address'))) AS Addr,
|
||||
JSON_VALUE(CASE WHEN ISJSON(r.TagConfig) = 1 THEN r.TagConfig END, '$.dataType') AS Dt) a
|
||||
CROSS APPLY (
|
||||
SELECT N'rename: MaxRpm is now MotorSpeedPct — re-point the tag (and ScadaBridge)' AS Finding, N'attention' AS Severity WHERE a.Addr LIKE N'%MaxRpm%'
|
||||
UNION ALL SELECT N'rename: AA_1/AA_2 are now the real axis names (AA6/AA7 on Z-34184) — re-point', N'attention' WHERE a.Addr LIKE N'%AA[_]1%' OR a.Addr LIKE N'%AA[_]2%'
|
||||
UNION ALL SELECT N'refused: String on a PMC/PARAM/MACRO address — driver skips the tag', N'attention'
|
||||
WHERE LOWER(a.Dt) = N'string' AND LOWER(LEFT(a.Addr, 6)) <> N'fixed:' AND LOWER(LEFT(a.Addr, 6)) <> N'alarm:'
|
||||
UNION ALL SELECT N'value change: position now scaled by its envelope', N'info' WHERE LOWER(a.Addr) LIKE N'fixed:axes/%' AND LOWER(a.Addr) LIKE N'%position%'
|
||||
UNION ALL SELECT N'value change: load now a real percent (spindle Load Float64)', N'info' WHERE LOWER(a.Addr) LIKE N'fixed:%load%'
|
||||
UNION ALL SELECT N'value change: macro decoded correctly (magnitude changes); vacant = BadNoData', N'info' WHERE LOWER(a.Addr) LIKE N'macro:%'
|
||||
UNION ALL SELECT N'value change: unreported program field reads BadNoData, not Good 0', N'info' WHERE LOWER(a.Addr) LIKE N'fixed:program%'
|
||||
) f
|
||||
WHERE r.DriverType = N'FOCAS';
|
||||
SELECT N'13' AS check_id, N'FOCAS address renames / refusals / value changes' AS title,
|
||||
CASE WHEN SUM(CASE WHEN severity = N'attention' THEN 1 ELSE 0 END) > 0 THEN N'attention' WHEN COUNT(*) > 0 THEN N'info' ELSE N'ok' END AS status,
|
||||
COUNT(*) AS row_count, N'NO (warn at deploy; client-visible)' AS blocking, N'commits 1dba46bd, 9460da51, cc140593, 69e0a622, 665ecd55, 26b512ad' AS source FROM @c13;
|
||||
SELECT * FROM @c13 ORDER BY severity, RawPath;
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------
|
||||
Check 14 — inventory (INFO): what the box actually runs, so the owner can discount driver-specific rows.
|
||||
--------------------------------------------------------------------------------------------- */
|
||||
SELECT N'14' AS check_id, N'Inventory' AS title, N'info' AS status,
|
||||
(SELECT COUNT(*) FROM dbo.DriverInstance) AS row_count, N'NO' AS blocking, N'-' AS source;
|
||||
SELECT d.DriverType, COUNT(DISTINCT d.DriverInstanceId) AS drivers, SUM(CASE WHEN d.Enabled = 1 THEN 1 ELSE 0 END) AS enabled_driver_rows,
|
||||
(SELECT COUNT(*) FROM @RawTag r WHERE r.DriverType = d.DriverType COLLATE Latin1_General_BIN2) AS raw_tags
|
||||
FROM dbo.DriverInstance d GROUP BY d.DriverType
|
||||
UNION ALL SELECT N'(VirtualTag)', COUNT(*), SUM(CASE WHEN Enabled = 1 THEN 1 ELSE 0 END), NULL FROM dbo.VirtualTag
|
||||
UNION ALL SELECT N'(ScriptedAlarm)', COUNT(*), SUM(CASE WHEN Enabled = 1 THEN 1 ELSE 0 END), NULL FROM dbo.ScriptedAlarm
|
||||
UNION ALL SELECT N'(Script)', COUNT(*), NULL, NULL FROM dbo.Script
|
||||
UNION ALL SELECT N'(UnsTagReference)', COUNT(*), NULL, NULL FROM dbo.UnsTagReference
|
||||
UNION ALL SELECT N'(ApiKey)', COUNT(*), SUM(CASE WHEN Enabled = 1 THEN 1 ELSE 0 END), NULL FROM dbo.ApiKey
|
||||
UNION ALL SELECT N'(ClusterNode)', COUNT(*), SUM(CASE WHEN Enabled = 1 THEN 1 ELSE 0 END), NULL FROM dbo.ClusterNode;
|
||||
Reference in New Issue
Block a user