feat(db-*): после загрузки расширения проверяется его применимость

Платформа отчитывается успехом и о расширении, которое не применит: отказ
всплывает лениво, при первом вызове метода, записью в журнал регистрации.
Теперь db-load-xml, db-load-git, db-load-cf и db-update после успешной операции
с расширением спрашивают платформу явно и печатают предупреждение; код возврата
операции не меняется — применение неприменимого расширения не разрушительно,
платформа просто работает по оригиналу. Поднять код возврата может -StrictLog,
он для регрессов и в инструкциях не значится.

Проверка обязана быть ОТДЕЛЬНЫМ запуском платформы: в одной командной строке
DESIGNER выполняет только последнюю пакетную команду, и дописанная проверка
отменила бы саму загрузку (замер: /LoadConfigFromFiles + /CheckCanApply… → код 0,
пустой лог, расширение в базе не изменилось). Об этом сказано в теле функции,
чтобы «оптимизация» не вернула команды в одну строку.

Выключатель: ключ -NoApplyCheck и настройка проекта extensionApplyCheck (ключ
команды сильнее). В ветке ibcmd проверка идёт соседним 1cv8; если его рядом нет —
одна строка [note], а не тишина.

Общий блок (Invoke-ApplyCheck / Invoke-ApplyCheckReport / Get-ApplyCheckEnabled и
их py-двойники) объявлен семьёй в check-inline-drift.mjs: разъехавшиеся копии
означали бы, что один навык предупреждает, а соседний по той же операции молчит.
db-load-cf получил недостающие копии Find-V8Project и ключ -StrictLog.

Раннер: у фейковой платформы появился отдельный ответ на проверку (spec.check),
выбираемый по составу аргументов, — иначе сценарий «загрузка прошла, проверка
провалилась» невыразим.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
This commit is contained in:
Nick Shirokov
2026-09-05 14:06:59 +03:00
co-authored by Claude Opus 5
parent 5943828216
commit a3ea0b7be6
28 changed files with 1137 additions and 53 deletions
@@ -116,6 +116,10 @@ param(
# но в логе есть отбраковка.
[switch]$StrictLog,
# Пропустить проверку применимости расширения после загрузки.
[Parameter(Mandatory=$false)]
[switch]$NoApplyCheck,
[Parameter(Mandatory=$false)]
[string]$RepositoryPath,
@@ -573,7 +577,74 @@ function Find-SilentRejections {
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
# Постусловие применимости расширения: платформа отчитывается успехом и о расширении, которое
# не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал регистрации.
#
# Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной командной
# строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча отбрасывает —
# проверено на 8.3.24, /LoadConfigFromFiles вместе с /CheckCanApplyConfigurationExtensions
# завершились кодом 0 с пустым логом, и загрузка не состоялась.
#
# Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний исполняемый файл.
function Invoke-ApplyCheck {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$exeDir = Split-Path $Exe -Parent
$exeLeaf = Split-Path $Exe -Leaf
$v8 = if ($exeLeaf -match '^ibcmd') { Join-Path $exeDir ("1cv8" + [System.IO.Path]::GetExtension($Exe)) } else { $Exe }
if (-not (Test-Path $v8)) { return @{ Skipped = $true; Reason = "1cv8 not found at $v8"; ExitCode = 0; Lines = @() } }
$dir = Join-Path $env:TEMP "apply_check_$(Get-Random)"
New-Item -ItemType Directory -Path $dir -Force | Out-Null
try {
$a = @("DESIGNER") + $ConnArgs + @("/CheckCanApplyConfigurationExtensions")
if ($Extension) { $a += "-Extension", "`"$Extension`"" }
$outFile = Join-Path $dir "check_log.txt"
$a += "/Out", "`"$outFile`"", "/DisableStartupDialogs"
$a += $ExtraArgs
$res = Invoke-PlatformProcess $v8 $a -PreQuoted
$lines = @()
if (Test-Path $outFile) {
$raw = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
if ($raw) { $lines = @($raw -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) }
}
return @{ Skipped = $false; Reason = ''; ExitCode = $res.ExitCode; Lines = $lines }
} finally {
if (Test-Path $dir) { Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue }
}
}
# Проверить и напечатать. $true, если платформа расширение не применит — вызывающий решает,
# поднимать ли код возврата (строгий режим).
function Invoke-ApplyCheckReport {
param([string]$Exe, [string[]]$ConnArgs, [string]$Extension, [string[]]$ExtraArgs)
$ac = Invoke-ApplyCheck $Exe $ConnArgs $Extension $ExtraArgs
if ($ac.Skipped) {
Write-Host "[note] applicability check skipped: $($ac.Reason)" -ForegroundColor Yellow
return $false
}
if ($ac.ExitCode -ne 0 -or $ac.Lines.Count -gt 0) {
Write-Host "[warning] the extension is loaded, but the platform will not apply it:" -ForegroundColor Yellow
foreach ($l in $ac.Lines) { Write-Host " $l" -ForegroundColor Yellow }
return $true
}
return $false
}
# Проверять ли применимость: -NoApplyCheck сильнее настройки проекта.
function Get-ApplyCheckEnabled {
param([switch]$Disabled)
if ($Disabled) { return $false }
$pf = Find-V8Project (Get-Location).Path
if ($pf) {
try {
$proj = Get-Content $pf -Raw -Encoding UTF8 | ConvertFrom-Json
if ($null -ne $proj.extensionApplyCheck) { return [bool]$proj.extensionApplyCheck }
} catch {}
}
return $true
}
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
if ($engine -eq "ibcmd") {
if (-not $InfoBasePath) {
Write-Host "Error: ibcmd supports file infobases only (use -InfoBasePath)" -ForegroundColor Red
@@ -782,6 +853,13 @@ try {
} else {
Write-Host "Error updating database configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
}
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
$acConn = @("/F", "`"$InfoBasePath`"")
if ($UserName) { $acConn += "/N`"$UserName`"" }
if ($Password) { $acConn += "/P`"$Password`"" }
if ((Invoke-ApplyCheckReport $V8Path $acConn $Extension @()) -and $StrictLog) { $exitCode = 1 }
}
Write-PlatformOutput $applyOut
}
exit $exitCode
@@ -794,21 +872,24 @@ try {
[System.IO.File]::WriteAllLines($listFile, $configFiles, $utf8Bom)
# --- Build arguments ---
$arguments = @("DESIGNER")
# Аргументы соединения собираем отдельно: тем же набором пойдёт запуск проверки применимости.
$connArgs = @()
if ($InfoBaseServer -and $InfoBaseRef) {
$arguments += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
$connArgs += "/S", "`"$InfoBaseServer/$InfoBaseRef`""
} else {
$arguments += "/F", "`"$InfoBasePath`""
$connArgs += "/F", "`"$InfoBasePath`""
}
if ($UserName) { $arguments += "/N`"$UserName`"" }
if ($Password) { $arguments += "/P`"$Password`"" }
if ($UserName) { $connArgs += "/N`"$UserName`"" }
if ($Password) { $connArgs += "/P`"$Password`"" }
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
$__repo = Resolve-RepositorySettings
$arguments += Get-RepositoryArgs $__repo
$connArgs += Get-RepositoryArgs $__repo
$arguments = @("DESIGNER") + $connArgs
$arguments += "/LoadConfigFromFiles", "`"$ConfigDir`""
$arguments += "-listFile", "`"$listFile`""
@@ -872,6 +953,11 @@ try {
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
}
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if ($exitCode -eq 0 -and ($Extension -or $AllExtensions) -and (Get-ApplyCheckEnabled -Disabled:$NoApplyCheck)) {
if ((Invoke-ApplyCheckReport $V8Path $connArgs $Extension $extraArgs) -and $StrictLog) { $exitCode = 1 }
}
exit $exitCode
} finally {
@@ -517,6 +517,76 @@ def find_silent_rejections(log_text):
return found
def run_apply_check(exe, conn_args, extension, extra_args):
"""Постусловие применимости расширения: платформа отчитывается успехом и о расширении,
которое не применит — отказ всплывает лениво, при первом вызове метода, записью в журнал
регистрации.
Запуск ОБЯЗАТЕЛЬНО отдельный. Дописать эту команду в строку операции нельзя: в одной
командной строке DESIGNER выполняет только ПОСЛЕДНЮЮ пакетную команду, остальные молча
отбрасывает — проверено на 8.3.24, /LoadConfigFromFiles вместе с
/CheckCanApplyConfigurationExtensions завершились кодом 0 с пустым логом, и загрузка не
состоялась.
Проверку умеет только 1cv8; если навык работал через ibcmd, берём соседний файл.
"""
exe_dir = os.path.dirname(exe)
leaf = os.path.basename(exe)
if leaf.lower().startswith("ibcmd"):
v8 = os.path.join(exe_dir, "1cv8" + os.path.splitext(leaf)[1])
else:
v8 = exe
if not os.path.isfile(v8):
return {"skipped": True, "reason": f"1cv8 not found at {v8}", "exit": 0, "lines": []}
temp_dir = tempfile.mkdtemp(prefix="apply_check_")
try:
a = ["DESIGNER"] + list(conn_args) + ["/CheckCanApplyConfigurationExtensions"]
if extension:
a += ["-Extension", f'"{extension}"']
out_file = os.path.join(temp_dir, "check_log.txt")
a += ["/Out", f'"{out_file}"', "/DisableStartupDialogs"]
a += list(extra_args)
r = run_v8(v8, a)
lines = []
if os.path.isfile(out_file):
with open(out_file, encoding="utf-8-sig", errors="replace") as f:
lines = [x.strip() for x in f.read().splitlines() if x.strip()]
return {"skipped": False, "reason": "", "exit": r.returncode, "lines": lines}
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def apply_check_report(exe, conn_args, extension, extra_args):
"""Проверить и напечатать. True, если платформа расширение не применит — вызывающий решает,
поднимать ли код возврата (строгий режим)."""
ac = run_apply_check(exe, conn_args, extension, extra_args)
if ac["skipped"]:
print(f"[note] applicability check skipped: {ac['reason']}")
return False
if ac["exit"] != 0 or ac["lines"]:
print("[warning] the extension is loaded, but the platform will not apply it:")
for line in ac["lines"]:
print(f" {line}")
return True
return False
def apply_check_enabled(disabled):
"""Проверять ли применимость: -NoApplyCheck сильнее настройки проекта."""
if disabled:
return False
pf = _sg_find_v8project(os.getcwd())
if pf:
try:
with open(pf, encoding="utf-8-sig") as f:
proj = json.load(f)
if proj.get("extensionApplyCheck") is not None:
return bool(proj.get("extensionApplyCheck"))
except Exception:
pass
return True
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
@@ -626,6 +696,8 @@ def main():
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
# но в логе есть отбраковка.
parser.add_argument("-StrictLog", action="store_true")
# Пропустить проверку применимости расширения после загрузки.
parser.add_argument("-NoApplyCheck", action="store_true")
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
@@ -828,6 +900,17 @@ def main():
else:
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}")
print_platform_output(ar)
# Проверку применимости умеет только 1cv8 — соединение для неё собираем в его форме.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
ac_conn = ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
ac_conn.append(f'/N"{args.UserName}"')
if args.Password:
ac_conn.append(f'/P"{args.Password}"')
if apply_check_report(v8path, ac_conn, args.Extension, []) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
# --- Write list file (UTF-8 with BOM) ---
@@ -836,22 +919,25 @@ def main():
f.write("\n".join(config_files))
# --- Build arguments ---
arguments = ["DESIGNER"]
# Аргументы соединения собираем отдельно: тем же набором пойдёт проверка применимости.
conn_args = []
if args.InfoBaseServer and args.InfoBaseRef:
arguments += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
conn_args += ["/S", f'"{args.InfoBaseServer}/{args.InfoBaseRef}"']
else:
arguments += ["/F", f'"{args.InfoBasePath}"']
conn_args += ["/F", f'"{args.InfoBasePath}"']
if args.UserName:
arguments.append(f'/N"{args.UserName}"')
conn_args.append(f'/N"{args.UserName}"')
if args.Password:
arguments.append(f'/P"{args.Password}"')
conn_args.append(f'/P"{args.Password}"')
# База под хранилищем не примет НИ ОДНОЙ операции конфигуратора без этих реквизитов, а для
# базы вне хранилища они безвредны — поэтому подставляем всегда, когда они известны.
repo = resolve_repository_settings(args)
arguments.extend(repository_args(repo))
conn_args.extend(repository_args(repo))
arguments = ["DESIGNER"] + conn_args
arguments += ["/LoadConfigFromFiles", f'"{args.ConfigDir}"']
arguments += ["-listFile", f'"{list_file}"']
@@ -919,6 +1005,12 @@ def main():
if args.StrictLog and exit_code == 0:
exit_code = 1
# Расширение могло загрузиться «успешно» и остаться неприменимым — спрашиваем платформу.
if (exit_code == 0 and (args.Extension or args.AllExtensions)
and apply_check_enabled(args.NoApplyCheck)):
if apply_check_report(v8path, conn_args, args.Extension, extra_args) and args.StrictLog:
exit_code = 1
sys.exit(exit_code)
finally: