param( [string]$Server = $env:ONEC_SQL_SERVER, [string]$Database = $env:ONEC_SQL_DATABASE, [string]$User = $env:ONEC_SQL_USER, [string]$Password = $env:ONEC_SQL_PASSWORD, [string]$OutputDir = "reports\1c-sql\saved-state-object-report", [string]$BaseMetadataDir = "reports\1c-sql\upo\structured-metadata-all-kinds", [string]$ExtensionGuidIndex = "reports\1c-sql\upo\xml-guid-index-extensions.json", [string]$ExtensionManifestSummary = "reports\1c-sql\upo\extension-manifest-xml-part-summary.json", [string]$ConfigCASAllDir = "reports\1c-sql\upo\ConfigCAS-all", [string]$Python = "python", [string]$MarkdownOutput = "", [int]$MarkdownMaxDiffLines = 80, [switch]$SkipMarkdown ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" if (-not $Server) { throw "Server is required. Use -Server or ONEC_SQL_SERVER." } if (-not $Database) { throw "Database is required. Use -Database or ONEC_SQL_DATABASE." } if (-not $User) { throw "User is required. Use -User or ONEC_SQL_USER." } if (-not $Password) { throw "Password is required. Use -Password or ONEC_SQL_PASSWORD." } $repoRoot = (Resolve-Path ".").ProviderPath $resolvedOutput = [System.IO.Path]::GetFullPath($OutputDir) New-Item -ItemType Directory -Force -Path $resolvedOutput | Out-Null $comparisonPath = Join-Path $resolvedOutput "saved-state-object-comparison.json" $detailPath = Join-Path $resolvedOutput "saved-state-object-details.json" $checkPath = Join-Path $resolvedOutput "saved-state-object-report-check.json" $configSaveDir = Join-Path $resolvedOutput "ConfigSave" $configCASSaveDir = Join-Path $resolvedOutput "ConfigCASSave" $activeConfigDir = Join-Path $resolvedOutput "ActiveConfig" $activeConfigCASDir = Join-Path $resolvedOutput "ActiveConfigCAS" foreach ($dir in @($configSaveDir, $configCASSaveDir, $activeConfigDir, $activeConfigCASDir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } function Invoke-Step { param( [string]$Name, [scriptblock]$Script ) $started = Get-Date & $Script [pscustomobject]@{ name = $Name started_at = $started.ToString("o") finished_at = (Get-Date).ToString("o") passed = $true } } function Read-JsonFile { param([string]$Path) return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json } function Export-TableFiles { param( [string]$Table, [string]$TargetDir, [string[]]$FileName ) $args = @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "scripts\export_1c_sql_files.ps1", "-Server", $Server, "-Database", $Database, "-User", $User, "-Password", $Password, "-Table", $Table, "-OutputPath", $TargetDir ) [object[]]$normalizedFileName = @($FileName) if (($normalizedFileName | Measure-Object).Count -gt 0) { $args += "-FileName" $args += $normalizedFileName } $json = & powershell @args if ($LASTEXITCODE -ne 0) { throw "Export failed for $Table." } return ($json | Out-String | ConvertFrom-Json) } function Get-JsonProperty { param( [object]$Object, [string]$Name, [object]$Default = $null ) if ($null -eq $Object) { return $Default } if ($Object.PSObject.Properties.Name -contains $Name) { return $Object.$Name } return $Default } function Join-LimitedValues { param( [object[]]$Values, [int]$Limit = 12 ) $items = @($Values | Where-Object { $null -ne $_ -and [string]$_ -ne "" } | Select-Object -First $Limit) return ,$items } function New-AgentSummary { param( [object]$Comparison, [object]$Detail ) $detailByName = @{} foreach ($item in @($Detail.object_details)) { $fullName = [string](Get-JsonProperty -Object $item -Name "full_name" -Default "") if ($fullName) { $detailByName[$fullName] = $item } } $objects = @() foreach ($change in @($Comparison.object_changes)) { $fullName = [string](Get-JsonProperty -Object $change -Name "full_name" -Default (Get-JsonProperty -Object $change -Name "name" -Default "")) $item = $null if ($fullName -and $detailByName.ContainsKey($fullName)) { $item = $detailByName[$fullName] } $parts = @() $addedTerms = @() $removedTerms = @() $textDiffParts = 0 $activeMissingParts = 0 foreach ($part in @((Get-JsonProperty -Object $item -Name "details" -Default @()))) { $payload = Get-JsonProperty -Object $part -Name "payload" -Default $null $diff = Get-JsonProperty -Object $payload -Name "text_diff" -Default $null $semanticHints = Get-JsonProperty -Object $payload -Name "semantic_hints" -Default $null $activeExists = Get-JsonProperty -Object $part -Name "active_exists" -Default $null if ($activeExists -eq $false) { $activeMissingParts += 1 } if ($null -ne $diff) { $textDiffParts += 1 $addedTerms += @(Get-JsonProperty -Object $semanticHints -Name "added_terms" -Default @()) $removedTerms += @(Get-JsonProperty -Object $semanticHints -Name "removed_terms" -Default @()) } $parts += [pscustomobject]@{ file_name = Get-JsonProperty -Object $part -Name "file_name" -Default $null payload_role = Get-JsonProperty -Object $part -Name "payload_role" -Default $null saved_table = Get-JsonProperty -Object $part -Name "saved_table" -Default $null active_table = Get-JsonProperty -Object $part -Name "active_table" -Default $null active_exists = $activeExists text_comparable = Get-JsonProperty -Object $payload -Name "text_comparable" -Default $null summary = Get-JsonProperty -Object $payload -Name "summary" -Default $null delta_chars = Get-JsonProperty -Object $diff -Name "delta_chars" -Default $null } } $objects += [pscustomobject]@{ full_name = $fullName layer = Get-JsonProperty -Object $change -Name "layer" -Default $null extension = Get-JsonProperty -Object $change -Name "extension" -Default $null kind = Get-JsonProperty -Object $change -Name "kind" -Default $null kind_ru = Get-JsonProperty -Object $change -Name "kind_ru" -Default $null name = Get-JsonProperty -Object $change -Name "name" -Default $null synonym = Get-JsonProperty -Object $change -Name "synonym" -Default $null change_state = Get-JsonProperty -Object $change -Name "change_state" -Default $null parts_count = ($parts | Measure-Object).Count text_diff_parts = $textDiffParts active_missing_parts = $activeMissingParts added_terms = Join-LimitedValues -Values ($addedTerms | Sort-Object -Unique) -Limit 12 removed_terms = Join-LimitedValues -Values ($removedTerms | Sort-Object -Unique) -Limit 12 parts = $parts } } $systemChanges = @() foreach ($change in @($Comparison.system_changes)) { $systemChanges += [pscustomobject]@{ name = Get-JsonProperty -Object $change -Name "name" -Default $null layer = Get-JsonProperty -Object $change -Name "layer" -Default $null extension = Get-JsonProperty -Object $change -Name "extension" -Default $null } } return [pscustomobject]@{ purpose = "Compact agent-facing summary of saved-but-not-applied changes in 1C terms." default_next_action = "Inspect object details before generating or applying any code changes." object_changes = $objects system_changes = $systemChanges object_names = @($objects | ForEach-Object { $_.full_name }) system_change_names = @($systemChanges | ForEach-Object { $_.name }) } } $steps = @() $steps += Invoke-Step "compare_saved_state_objects" { & powershell -NoProfile -ExecutionPolicy Bypass -File scripts\compare_1c_saved_state_objects.ps1 ` -Server $Server ` -Database $Database ` -User $User ` -Password $Password ` -BaseMetadataDir $BaseMetadataDir ` -ExtensionGuidIndex $ExtensionGuidIndex ` -Output $comparisonPath | Out-Null if ($LASTEXITCODE -ne 0) { throw "Saved-state object comparison failed." } } $comparison = Read-JsonFile $comparisonPath $steps += Invoke-Step "export_config_save" { $null = Export-TableFiles -Table "ConfigSave" -TargetDir $configSaveDir } $steps += Invoke-Step "export_config_cas_save" { $null = Export-TableFiles -Table "ConfigCASSave" -TargetDir $configCASSaveDir } $activeConfigFiles = @( $comparison.object_changes | ForEach-Object { $_.storage } | Where-Object { $_.active_table -eq "Config" } | ForEach-Object { [string]$_.file_name } | Sort-Object -Unique ) $activeConfigCASFiles = @( $comparison.object_changes | ForEach-Object { $_.storage } | Where-Object { $_.active_table -eq "ConfigCAS" -and $_.active_exists } | ForEach-Object { [string]$_.file_name } | Sort-Object -Unique ) $steps += Invoke-Step "export_active_config_payloads" { if (($activeConfigFiles | Measure-Object).Count -gt 0) { $null = Export-TableFiles -Table "Config" -TargetDir $activeConfigDir -FileName $activeConfigFiles } } $steps += Invoke-Step "export_active_config_cas_payloads" { if (($activeConfigCASFiles | Measure-Object).Count -gt 0) { $null = Export-TableFiles -Table "ConfigCAS" -TargetDir $activeConfigCASDir -FileName $activeConfigCASFiles } } $steps += Invoke-Step "analyze_saved_state_object_details" { & $Python scripts\analyze_1c_saved_state_object_details.py ` --comparison $comparisonPath ` --config-save-dir $configSaveDir ` --config-dir $activeConfigDir ` --config-cas-save-dir $configCASSaveDir ` --config-cas-dir $activeConfigCASDir ` --extension-manifest-summary $ExtensionManifestSummary ` --config-cas-all-dir $ConfigCASAllDir ` --output $detailPath | Out-Null if ($LASTEXITCODE -ne 0) { throw "Saved-state detail analysis failed." } } $detail = Read-JsonFile $detailPath $agentSummary = New-AgentSummary -Comparison $comparison -Detail $detail $reportPath = Join-Path $resolvedOutput "saved-state-object-report.json" if (-not $MarkdownOutput) { $MarkdownOutput = Join-Path $resolvedOutput "saved-state-object-report.md" } else { $MarkdownOutput = [System.IO.Path]::GetFullPath($MarkdownOutput) } $result = [pscustomobject]@{ schema = "onec_saved_state_object_report.v1" server = $Server database = $Database report = $reportPath markdown = if ($SkipMarkdown) { $null } else { $MarkdownOutput } check = $checkPath output_dir = $resolvedOutput comparison = $comparisonPath detail = $detailPath agent_summary = $agentSummary payload_dirs = [pscustomobject]@{ config_save = $configSaveDir config_cas_save = $configCASSaveDir active_config = $activeConfigDir active_config_cas = $activeConfigCASDir active_config_cas_all = $ConfigCASAllDir } counts = [pscustomobject]@{ object_changes = $comparison.counts.object_changes system_changes = $comparison.counts.system_changes detail_objects = $detail.counts.objects detail_parts = $detail.counts.details } steps = $steps safety = [pscustomobject]@{ read_only = $true sql_write_performed = $false public_terms_are_1c_objects = $true secrets_in_report = $false } } $result | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $reportPath -Encoding UTF8 if (-not $SkipMarkdown) { $steps += Invoke-Step "render_saved_state_markdown" { & $Python scripts\render_1c_saved_state_object_report_markdown.py ` --report $reportPath ` --output $MarkdownOutput ` --max-diff-lines $MarkdownMaxDiffLines | Out-Null if ($LASTEXITCODE -ne 0) { throw "Saved-state Markdown rendering failed." } } $result.steps = $steps $result | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $reportPath -Encoding UTF8 } $steps += Invoke-Step "check_saved_state_report" { & $Python scripts\check_1c_saved_state_object_report.py ` --report $reportPath ` --output $checkPath | Out-Null if ($LASTEXITCODE -ne 0) { throw "Saved-state report check failed." } } $result.steps = $steps $result | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $reportPath -Encoding UTF8 $result | ConvertTo-Json -Depth 12