feat(role-validate): версия формата Rights.xml — как у дескриптора роли; сверка с выгрузкой

Права и дескриптор роли платформа загружает только в одной версии формата («Версия формата
загружаемого файла … отличается от версии формата ранее загруженных файлов»), проверено на
8.3.27.1859 в обе стороны. Rights.xml не в версии Roles/<Имя>.xml — ошибка. Роль целиком в другой
версии, чем выгрузка, платформа грузит — это лишь неоднородность выгрузки (типично после мержа
веток, выгруженных разными платформами), поэтому такое расхождение только предупреждение.

Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-26 22:11:21 +03:00
co-authored by Sergei Pleshanov Claude Opus 5.5
parent 5a62b37017
commit 1736be0903
27 changed files with 1450 additions and 6 deletions
@@ -1,4 +1,4 @@
# role-validate v1.6 — Validate 1C role structure
# role-validate v1.7 — Validate 1C role structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -297,6 +297,61 @@ function Find-Similar {
return $result
}
# Штамп версии формата — атрибут version КОРНЕВОГО элемента файла. Копия общего эталона (семья
# root_version, авторитет — meta-validate).
function Get-RootVersion([string]$xmlPath) {
if (-not (Test-Path -LiteralPath $xmlPath -PathType Leaf)) { return $null }
$buf = New-Object byte[] 4096
$fs = [System.IO.File]::OpenRead($xmlPath)
try { $len = $fs.Read($buf, 0, $buf.Length) } finally { $fs.Dispose() }
$head = [System.Text.Encoding]::UTF8.GetString($buf, 0, $len)
$m = [regex]::Match($head, '<[A-Za-z_][\w.:-]*(\s[^>]*)?/?>')
if (-not $m.Success) { return $null }
$v = [regex]::Match($m.Groups[1].Value, '(?:^|\s)version="([^"]*)"')
if ($v.Success) { return $v.Groups[1].Value }
return $null
}
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья is_external_root,
# авторитет — cf-edit).
function Test-ExternalObjectRoot([string]$xmlPath) {
if (-not (Test-Path $xmlPath)) { return $false }
try {
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
$el = $mx.DocumentElement.FirstChild
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.LocalName }
} catch {}
return $false
}
# Якорь выгрузки: корень автономной EPF/ERF либо Configuration.xml, ближайший вверх. Копия общего
# эталона (семья find_dump_anchor, авторитет — meta-validate).
function Find-DumpAnchor([string]$startDir) {
$d = $startDir
for ($i = 0; $i -lt 15 -and $d; $i++) {
if (Test-ExternalObjectRoot "$d.xml") { return "$d.xml" }
$cfg = Join-Path $d "Configuration.xml"
if (Test-Path $cfg) { return $cfg }
$parent = [System.IO.Path]::GetDirectoryName($d)
if (-not $parent -or $parent -eq $d) { break }
$d = $parent
}
return $null
}
# Владелец тела X/Ext/<файл>.xml — дескриптор X.xml рядом с каталогом X. Копия общего эталона
# (семья ext_body_owner, авторитет — form-validate).
function Get-ExtBodyOwner([string]$bodyPath) {
$extDir = [System.IO.Path]::GetDirectoryName($bodyPath)
if ([System.IO.Path]::GetFileName($extDir) -cne "Ext") { return $null }
$objDir = [System.IO.Path]::GetDirectoryName($extDir)
if (Test-Path -LiteralPath "$objDir.xml" -PathType Leaf) { return "$objDir.xml" }
$cfg = Join-Path $objDir "Configuration.xml"
if (Test-Path -LiteralPath $cfg -PathType Leaf) { return $cfg }
return $null
}
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($RightsPath)) {
$RightsPath = Join-Path (Get-Location).Path $RightsPath
@@ -552,6 +607,26 @@ if (Test-Path $MetadataPath) {
}
}
# --- 4b. Format version: Rights.xml — как у дескриптора роли; сверка с выгрузкой ---
# Права и дескриптор роли платформа загружает только в одной версии формата: «Версия формата
# загружаемого файла … отличается от версии формата ранее загруженных файлов». С остальной выгрузкой
# роль может расходиться — платформа такое грузит, это лишь неоднородность выгрузки (типично после
# мержа веток, выгруженных разными платформами).
$rightsVer = $root.GetAttribute("version")
if ($rightsVer) {
$ownerPath = Get-ExtBodyOwner $resolvedRights
$ownerVer = if ($ownerPath) { Get-RootVersion $ownerPath } else { $null }
$dumpAnchor = Find-DumpAnchor (Split-Path $resolvedRights -Parent)
$dumpVer = if ($dumpAnchor) { Get-RootVersion $dumpAnchor } else { $null }
if ($ownerVer -and $rightsVer -ne $ownerVer) {
Report-Error "Format version $rightsVer differs from the role descriptor $([System.IO.Path]::GetFileName($ownerPath)) ($ownerVer) — the platform refuses to load parts of one object in different formats"
} elseif ($dumpVer -and $rightsVer -ne $dumpVer) {
Report-Warn "Format version $rightsVer differs from the dump ($dumpVer) — the platform loads it, but the dump is no longer uniform (typical after merging branches dumped by different platforms)"
} elseif ($ownerVer -or $dumpVer) {
Report-OK "Format version: $rightsVer, matches the descriptor and the dump"
}
}
# --- 5. Check registration in Configuration.xml ---
$configDir = Split-Path $rolesDir -Parent
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-validate v1.6 — Validate 1C role Rights.xml structure
# role-validate v1.7 — Validate 1C role Rights.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates role Rights.xml: root element, global flags, objects, rights, RLS, templates."""
import sys, os, argparse, re
@@ -298,6 +298,71 @@ def get_child_el(parent, local_name, ns):
return None
# Штамп версии формата — атрибут version КОРНЕВОГО элемента файла. Копия общего эталона (семья
# root_version, авторитет — meta-validate).
def root_version(xml_path):
if not os.path.isfile(xml_path):
return None
with open(xml_path, "rb") as f:
head = f.read(4096).decode("utf-8", errors="ignore")
m = re.search(r'<[A-Za-z_][\w.:-]*(\s[^>]*)?/?>', head)
if not m:
return None
v = re.search(r'(?:^|\s)version="([^"]*)"', m.group(1) or "")
if v:
return v.group(1)
return None
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья is_external_root,
# авторитет — cf-edit).
def _sg_is_external_root(xml_path):
if not os.path.isfile(xml_path):
return False
try:
mx = etree.parse(xml_path).getroot()
for child in mx:
if isinstance(child.tag, str):
return child.tag.split("}")[-1] in ("ExternalDataProcessor", "ExternalReport")
except Exception:
return False
return False
# Якорь выгрузки: корень автономной EPF/ERF либо Configuration.xml, ближайший вверх. Копия общего
# эталона (семья find_dump_anchor, авторитет — meta-validate).
def find_dump_anchor(start_dir):
d = start_dir
for _ in range(15):
if not d:
break
if _sg_is_external_root(d + ".xml"):
return d + ".xml"
cfg = os.path.join(d, "Configuration.xml")
if os.path.exists(cfg):
return cfg
parent = os.path.dirname(d)
if not parent or parent == d:
break
d = parent
return None
# Владелец тела X/Ext/<файл>.xml — дескриптор X.xml рядом с каталогом X. Копия общего эталона
# (семья ext_body_owner, авторитет — form-validate).
def ext_body_owner(body_path):
ext_dir = os.path.dirname(body_path)
if os.path.basename(ext_dir) != "Ext":
return None
obj_dir = os.path.dirname(ext_dir)
if os.path.isfile(obj_dir + ".xml"):
return obj_dir + ".xml"
cfg = os.path.join(obj_dir, "Configuration.xml")
if os.path.isfile(cfg):
return cfg
return None
def main():
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
@@ -600,6 +665,27 @@ def main():
except etree.XMLSyntaxError as e:
report_error(f'Metadata XML parse error: {e}')
# --- 4b. Format version: Rights.xml — как у дескриптора роли; сверка с выгрузкой ---
# Права и дескриптор роли платформа загружает только в одной версии формата: «Версия формата
# загружаемого файла … отличается от версии формата ранее загруженных файлов». С остальной выгрузкой
# роль может расходиться — платформа такое грузит, это лишь неоднородность выгрузки (типично после
# мержа веток, выгруженных разными платформами).
rights_ver = root.get('version', '')
if rights_ver:
owner_path = ext_body_owner(resolved_path)
owner_ver = root_version(owner_path) if owner_path else None
dump_anchor = find_dump_anchor(os.path.dirname(resolved_path))
dump_ver = root_version(dump_anchor) if dump_anchor else None
if owner_ver and rights_ver != owner_ver:
report_error(f'Format version {rights_ver} differs from the role descriptor '
f'{os.path.basename(owner_path)} ({owner_ver}) '
'— the platform refuses to load parts of one object in different formats')
elif dump_ver and rights_ver != dump_ver:
report_warn(f'Format version {rights_ver} differs from the dump ({dump_ver}) — the platform loads it, '
'but the dump is no longer uniform (typical after merging branches dumped by different platforms)')
elif owner_ver or dump_ver:
report_ok(f'Format version: {rights_ver}, matches the descriptor and the dump')
# --- 5. Check registration in Configuration.xml ---
config_dir = os.path.dirname(roles_dir) # config root
config_xml_path = os.path.join(config_dir, 'Configuration.xml')