fix(xdto): три дефекта, найденных платформенной верификацией снэпшотов

verify-snapshots загружает результат каждого кейса в 1С. Раньше навыки xdto
через него не проходили вовсе; первый прогон дал 5 из 9. Ни корпусная сверка,
ни валидатор такого не ловили: корпус состоит из заведомо валидных пакетов,
а синтетические кейсы до сих пор в базу не грузились.

1. fixed. В модели XDTO это булев флаг, значение лежит в default; в XSD наоборот —
   fixed="V" несёт значение. Компилятор писал значение прямо в fixed, и платформа
   отвергала пакет («Отсутствует фиксированное значение свойства»). Перевод сделан
   в обе стороны; по принципу прощающего ввода принимается и модельная форма через
   зеркало xdto:fixed. Отображение выведено по корпусу: fixed встречается только
   вместе с default, значений всего два.

2. Импорт на несуществующий пакет платформа отвергает («xdto-package-3.3 …
   не определен»), а у нас проверки не было. Добавлена ошибка валидатора и,
   что важнее, предупреждение прямо на сборке — отказ при db-update дешевле
   поймать на шаг раньше. Правило пришлось калибровать корпусом: сначала оно
   дало 67 ложных срабатываний на платформенных пространствах имён, их список
   выведен и исключён.

3. localName проверяется как NCName — фикстура с пробелом в имени была негодной,
   заменена на реалистичный дефис (name="alpha_3" localName="alpha-3").

Харнесс получил skipPlatformVerify с обязательной причиной: результат
set-namespace невалиден by design, операция намеренно оставляет висящий импорт
у зависящего пакета.

Итог: 9/9 компилятора, 9/10 + 1 осознанный пропуск у edit, round-trip 760/760,
валидатор 0 ложных, 40 тестов на обоих рантаймах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-26 14:51:08 +03:00
co-authored by Claude Opus 5
parent 3eb805f7b0
commit 5d5a1bc36a
33 changed files with 344 additions and 29 deletions
@@ -15,6 +15,19 @@ param(
)
$ErrorActionPreference = "Stop"
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу: импортируются, но
# targetNamespace с таким значением ни у одного пакета нет)
$PLATFORM_NS = @(
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema"
)
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$XDTO_NS = "http://v8.1c.ru/8.1/xdto"
@@ -446,8 +459,18 @@ function Build-Property([System.Xml.XmlElement]$el, [bool]$isAttribute) {
Add-Attr $p "nillable" (XA $el "nillable")
}
Add-Attr $p "fixed" (XA $el "fixed")
Add-Attr $p "default" (XA $el "default")
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
$mFixed = MA $el "fixed"
if ($null -ne $mFixed) {
Add-Attr $p "fixed" $mFixed
Add-Attr $p "default" (XA $el "default")
} elseif ($null -ne (XA $el "fixed")) {
Add-Attr $p "fixed" "true"
Add-Attr $p "default" (XA $el "fixed")
} else {
Add-Attr $p "default" (XA $el "default")
}
if ($isAttribute) {
Add-Attr $p "form" "Attribute"
@@ -839,6 +862,28 @@ M "`t</XDTOPackage>"
# --- Register in Configuration.xml ---
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
$xdtoRootDir = Join-Path $OutputDir "XDTOPackages"
$declaredImports = @()
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "import") { foreach ($a in $c.Attrs) { if ($a.Name -eq "namespace") { $declaredImports += $a.Value } } } }
if ($declaredImports.Count -gt 0 -and (Test-Path $xdtoRootDir)) {
$knownNs = @{}
foreach ($other in (Get-ChildItem $xdtoRootDir -Directory -ErrorAction SilentlyContinue)) {
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
if (-not (Test-Path $ob)) { continue }
try {
$od = New-Object System.Xml.XmlDocument
$od.Load($ob)
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $true
} catch {}
}
foreach ($imp in $declaredImports) {
if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) {
Warn "Импорт `"$imp`" не разрешается: пакета с таким namespace в конфигурации нет. Платформа отвергнет пакет при обновлении — соберите зависимость первой"
}
}
}
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
$regResult = "no-config"
if (Test-Path $configXmlPath) {
@@ -9,6 +9,18 @@ import uuid
from lxml import etree
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу)
PLATFORM_NS = {
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema",
}
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
@@ -498,8 +510,17 @@ def build_property(el, is_attribute):
add_attr(p, "upperBound", "-1" if max_occ == "unbounded" else max_occ)
add_attr(p, "nillable", el.get("nillable"))
add_attr(p, "fixed", el.get("fixed"))
add_attr(p, "default", el.get("default"))
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
m_fixed = MA(el, "fixed")
if m_fixed is not None:
add_attr(p, "fixed", m_fixed)
add_attr(p, "default", el.get("default"))
elif el.get("fixed") is not None:
add_attr(p, "fixed", "true")
add_attr(p, "default", el.get("fixed"))
else:
add_attr(p, "default", el.get("default"))
if is_attribute:
add_attr(p, "form", "Attribute")
@@ -869,6 +890,25 @@ with open(md_file, "wb") as f:
# ── register in Configuration.xml ────────────────────────────
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
xdto_root_dir = os.path.join(args.OutputDir, "XDTOPackages")
declared_imports = [a["value"] for c in pkg_node.children if c.tag == "import"
for a in c.attrs if a["name"] == "namespace"]
if declared_imports and os.path.isdir(xdto_root_dir):
known_ns = set()
for other in sorted(os.listdir(xdto_root_dir)):
ob = os.path.join(xdto_root_dir, other, "Ext", "Package.bin")
if not os.path.exists(ob):
continue
try:
known_ns.add(_parse_xml(ob).getroot().get("targetNamespace"))
except Exception: # noqa: BLE001
pass
for imp in declared_imports:
if imp not in known_ns and imp not in PLATFORM_NS:
warn(f'Импорт "{imp}" не разрешается: пакета с таким namespace в конфигурации нет. '
"Платформа отвергнет пакет при обновлении — соберите зависимость первой")
config_xml = os.path.join(args.OutputDir, "Configuration.xml")
reg_result = "no-config"
if os.path.exists(config_xml):
@@ -302,6 +302,13 @@ function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlob
$nill = A $p "nillable"
$def = A $p "default"
$fix = A $p "fixed"
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
# fixed="V" несёт само значение. Переводим, а не копируем.
$defOut = $def
$fixOut = $null
$fixMirror = ""
if ($fix -eq "true" -and $null -ne $def) { $fixOut = $def; $defOut = $null }
elseif ($null -ne $fix) { $fixMirror = Mirror "fixed" $fix }
$anon = Get-AnonTypeDef $p
# qualified записан как атрибут в пространстве имён XDTO
$qual = $p.GetAttribute("qualified", $XDTO_NS)
@@ -326,7 +333,8 @@ function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlob
if ($null -ne $lower) { $m += Mirror "lowerBound" $lower }
if ($null -ne $upper) { $m += Mirror "upperBound" $upper }
$m += $mirrorName
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $def, 'fixed', $fix)
$m += $fixMirror
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $defOut, 'fixed', $fixOut)
if ($anon) {
X "$indent<xs:attribute$body$m>"
X "$indent`t<xs:simpleType>"
@@ -349,10 +357,11 @@ function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlob
if ($null -ne $qual) { $m += Mirror "qualified" $qual }
$m += $mirrorName
$m += (Mirror-Prefix $p)
$m += $fixMirror
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type,
'minOccurs', $minOccurs, 'maxOccurs', $maxOccurs,
'nillable', $nill, 'default', $def, 'fixed', $fix)
'nillable', $nill, 'default', $defOut, 'fixed', $fixOut)
if ($anon) {
X "$indent<xs:element$body$m>"
@@ -308,6 +308,13 @@ def emit_property(p, indent):
nill = p.get("nillable")
default = p.get("default")
fixed = p.get("fixed")
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
# fixed="V" несёт само значение. Переводим, а не копируем.
def_out, fix_out, fix_mirror = default, None, ""
if fixed == "true" and default is not None:
fix_out, def_out = default, None
elif fixed is not None:
fix_mirror = mirror("fixed", fixed)
anon = anon_type_def(p)
qual = p.get(f"{{{XDTO_NS}}}qualified")
@@ -330,7 +337,8 @@ def emit_property(p, indent):
if upper is not None:
m += mirror("upperBound", upper)
m += mirror_name
body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", default, "fixed", fixed])
m += fix_mirror
body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", def_out, "fixed", fix_out])
if anon is not None:
X(f"{indent}<xs:attribute{body}{m}>")
X(f"{indent}\t<xs:simpleType>")
@@ -351,10 +359,11 @@ def emit_property(p, indent):
m += mirror("qualified", qual)
m += mirror_name
m += mirror_prefix(p)
m += fix_mirror
body = attrs(["name", xml_name, "ref", ref, "type", type_,
"minOccurs", min_occurs, "maxOccurs", max_occurs,
"nillable", nill, "default", default, "fixed", fixed])
"nillable", nill, "default", def_out, "fixed", fix_out])
if anon is not None:
X(f"{indent}<xs:element{body}{m}>")
@@ -15,6 +15,19 @@ param(
)
$ErrorActionPreference = "Stop"
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу: импортируются, но
# targetNamespace с таким значением ни у одного пакета нет)
$PLATFORM_NS = @(
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema"
)
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$XS_NS = "http://www.w3.org/2001/XMLSchema"
@@ -504,6 +517,27 @@ if (Test-Path $configXml) {
if ($od.DocumentElement.GetAttribute("targetNamespace") -eq $targetNs) { $clash += $other.Name }
} catch {}
}
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
$knownNs = @{}
foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) {
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
if (-not (Test-Path $ob)) { continue }
try {
$od = New-Object System.Xml.XmlDocument
$od.Load($ob)
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $other.Name
} catch {}
}
$missing = @()
foreach ($imp in $imports) { if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) { $missing += $imp } }
if ($missing.Count -gt 0) {
Report-Error ("Импортируемые пакеты не определены в конфигурации: " + ($missing -join ", ") +
". Платформа отвергнет пакет при обновлении конфигурации — соберите зависимости первыми")
} elseif ($imports.Count -gt 0) {
Report-OK "Все импорты разрешаются в пакеты конфигурации"
}
if ($clash.Count -gt 0) {
Report-Warn "targetNamespace `"$targetNs`" объявлен также в пакет(ах): $($clash -join ', '). Платформа это допускает, но <import> на это пространство имён становится неоднозначным"
} else {
@@ -6,6 +6,18 @@ import sys
from lxml import etree
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу)
PLATFORM_NS = {
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema",
}
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
@@ -510,6 +522,26 @@ if os.path.exists(config_xml):
clash.append(other)
except Exception: # noqa: BLE001
pass
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
known_ns = {}
for other in sorted(os.listdir(pkg_root)):
ob = os.path.join(pkg_root, other, "Ext", "Package.bin")
if not os.path.exists(ob):
continue
try:
known_ns[_parse_xml(ob).getroot().get("targetNamespace")] = other
except Exception: # noqa: BLE001
pass
missing_imports = [i for i in imports if i not in known_ns and i not in PLATFORM_NS]
if missing_imports:
report_error("Импортируемые пакеты не определены в конфигурации: "
+ ", ".join(missing_imports)
+ ". Платформа отвергнет пакет при обновлении конфигурации — "
"соберите зависимости первыми")
elif imports:
report_ok("Все импорты разрешаются в пакеты конфигурации")
if clash:
report_warn(f'targetNamespace "{target_ns}" объявлен также в пакет(ах): {", ".join(clash)}. '
"Платформа это допускает, но <import> на это пространство имён становится неоднозначным")