fix(db-load-git,db-update): ловить тихие отказы платформы, как db-load-xml

Платформа рапортует об успехе (exit 0) и одновременно пишет в /Out-лог, что часть
метаданных отброшена. Детектор этого был только в db-load-xml, хотя db-load-git гоняет
тот же /LoadConfigFromFiles (и дописывает /UpdateDBCfg в тот же вызов), а db-update —
вторую половину той же цепочки. Оба лог печатали, но не разбирали.

Детектор извлечён в Find-SilentRejections / find_silent_rejections и внесён в реестр
семей check-inline-drift: инлайн-код гард сверять не умеет, а именно расхождение копий
и было бы главным риском такого дублирования.

Добавлен восьмой паттерн — «Для работы с конфигурацией необходима версия платформы не
меньше». Замерено при работе над issue #63: конфигурация с режимом совместимости выше
платформы грузится с кодом 0, db-update тоже отвечает 0, объекты в базу не попадают, а
отказ приходит только в рантайме. Строка обрезана до инвариантной части — конкретная
версия в сообщении меняется.

Текст предупреждения переписан. Убрана подсказка «pass -StrictLog to treat as error»:
ключ предназначен для регрессов (его передаёт verify-snapshots), а совет бессмысленный —
операция уже выполнена, и повторять её ради того же текста незачем. Формулировка больше
не утверждает «dropped properties/refs»: класс проблемы разный, а строки лога печатаются
следом и говорят за себя.

Две правки по дороге:

- `return ,$found` в паре с `@()` у вызывающего давал массив из одного пустого массива,
  то есть предупреждение «1 problem(s)» на чистом логе. Возврат без запятой-обёртки.
- py-порт писал предупреждение в stderr, PS1 — в stdout. В самом py-порте stderr занят
  исключительно фатальными «Error:» перед exit 1, так что не-фатальное предупреждение там
  было единственным исключением. Приведено к stdout — и к соглашению своего же файла, и к
  поведению PS1; verify-snapshots на падении читает `stderr || stdout`, поэтому диагностика
  не теряется.

Тесты: фейковая платформа .cmd, которая вычитывает путь из /Out и кладёт туда готовый лог,
— по четыре кейса на db-load-xml и db-update (отбраковка, она же под -StrictLog, новый
паттерн, чистый лог без предупреждения). Раньше детектор не был покрыт вообще.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-13 15:14:36 +03:00
co-authored by Claude Opus 5
parent 06f21ab3d1
commit a024b7da9c
15 changed files with 627 additions and 52 deletions
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# db-load-git v1.19 — Load Git changes into 1C database
# db-load-git v1.20 — Load Git changes into 1C database
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -344,6 +344,38 @@ def print_platform_output(result):
print("--- End ---")
def find_silent_rejections(log_text):
"""Строки лога, о которых платформа сообщает, НЕ поднимая код возврата.
Метаданные отброшены или конфигурация нерабочая, а операция при этом «успешна».
Возвращает подошедшие строки.
Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки
автономны). Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет
весь смысл.
"""
patterns = [
"Неверное свойство объекта метаданных",
"не входит в состав объекта метаданных",
"Неизвестное имя типа",
"Неизвестный объект метаданных",
"Ни один из документов не является регистратором для регистра",
"Неверное значение перечисления",
"не может быть приведен к типу",
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в
# рантайме. Обрезано до инвариантной части — конкретная версия в сообщении меняется.
"Для работы с конфигурацией необходима версия платформы не меньше",
]
found = []
if log_text:
for line in log_text.splitlines():
for pat in patterns:
if pat in line:
found.append(line.strip())
break
return found
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -446,6 +478,10 @@ def main():
)
parser.add_argument("-DryRun", action="store_true", help="Only show what would be loaded (no actual load)")
parser.add_argument("-UpdateDB", action="store_true", help="Also update database configuration after load")
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но в логе есть отбраковка.
parser.add_argument("-StrictLog", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -705,6 +741,7 @@ def main():
else:
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
log_content = ""
if os.path.isfile(out_file):
try:
with open(out_file, "r", encoding="utf-8-sig") as f:
@@ -717,6 +754,21 @@ def main():
pass
print_platform_output(result)
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
# про -StrictLog не даём: операция уже выполнена, повторять её ради того же текста незачем.
silent_failures = find_silent_rejections(log_content)
if silent_failures:
print(
f"[warning] platform reported success, but the log contains "
f"{len(silent_failures)} problem(s):"
)
for line in silent_failures:
print(f" {line}")
if args.StrictLog and exit_code == 0:
exit_code = 1
sys.exit(exit_code)
finally: