diff --git a/.claude/skills/role-compile/SKILL.md b/.claude/skills/role-compile/SKILL.md
index a11995087..67ba38743 100644
--- a/.claude/skills/role-compile/SKILL.md
+++ b/.claude/skills/role-compile/SKILL.md
@@ -60,6 +60,12 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
`@` обязателен в shorthand. В объектной форме — `"preset": "view"` без `@`.
+### Зависимые права
+
+Набор прав дополняется до замыкания: `Edit` тянет `Read`, `Update`, `View`; интерактивные права —
+свой базовый набор; `View` у обработки и отчёта — `Use`. Дописанное перечисляется в выводе. Так же
+поступает сама платформа при загрузке, поэтому без этого файл роли и база расходятся.
+
### Сервисы
Платформа проверяет право на **вложенном объекте** сервиса — методе шаблона URL, операции, канале. Права на сервис целиком не существует.
diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1
index 13268eeb4..6954b57bd 100644
--- a/.claude/skills/role-compile/scripts/role-compile.ps1
+++ b/.claude/skills/role-compile/scripts/role-compile.ps1
@@ -1,4 +1,4 @@
-# role-compile v1.38 — Compile 1C role from JSON
+# role-compile v1.39 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -833,6 +833,112 @@ function Sort-ObjectsByUuid {
return $result
}
+# --- 4b. Зависимости прав (замерено на платформе) ---
+# Платформа при загрузке сама доводит набор до замыкания: выдал Edit — получил ещё
+# Read, Update и View. Пишем замыкание сразу, иначе файл и база расходятся.
+# Таблица общая для типов; исключения — там, где у типа своя механика (обработка и отчёт
+# держатся на Use, план счетов не тянет Read под историю данных).
+$script:rightDeps = @{
+ "Delete" = @("Read")
+ "Edit" = @("Read","Update","View")
+ "EditDataHistoryVersionComment" = @("Read","ReadDataHistory","UpdateDataHistoryVersionComment","View")
+ "Execute" = @("Read","Update")
+ "InputByString" = @("Read","View")
+ "Insert" = @("Read")
+ "InteractiveActivate" = @("Read","Update")
+ "InteractiveChangeOfPosted" = @("Edit","Read","Update","View")
+ "InteractiveClearDeletionMark" = @("Edit","Read","Update","View")
+ "InteractiveClearDeletionMarkPredefinedData" = @("Edit","InteractiveClearDeletionMark","Read","Update","View")
+ "InteractiveDelete" = @("Delete","Edit","Read","Update","View")
+ "InteractiveDeleteMarked" = @("Delete","Edit","Read","Update","View")
+ "InteractiveDeleteMarkedPredefinedData" = @("Delete","Edit","InteractiveDeleteMarked","Read","Update","View")
+ "InteractiveDeletePredefinedData" = @("Delete","Edit","InteractiveDelete","Read","Update","View")
+ "InteractiveExecute" = @("Execute","Read","Update")
+ "InteractiveInsert" = @("Edit","Insert","Read","Update","View")
+ "InteractivePosting" = @("Edit","Posting","Read","Update","View")
+ "InteractivePostingRegular" = @("Edit","InteractivePosting","Posting","Read","Update","View")
+ "InteractiveSetDeletionMark" = @("Edit","Read","Update","View")
+ "InteractiveSetDeletionMarkPredefinedData" = @("Edit","InteractiveSetDeletionMark","Read","Update","View")
+ "InteractiveStart" = @("Read","Start","Update")
+ "InteractiveUndoPosting" = @("Edit","Read","UndoPosting","Update","View")
+ "Posting" = @("Read","Update")
+ "ReadDataHistory" = @("Read")
+ "ReadDataHistoryOfMissingData" = @("Read","ReadDataHistory")
+ "Start" = @("Read","Update")
+ "SwitchToDataHistoryVersion" = @("Read","View")
+ "UndoPosting" = @("Read","Update")
+ "Update" = @("Read")
+ "UpdateDataHistory" = @("Read","ReadDataHistory")
+ "UpdateDataHistoryOfMissingData" = @("Read","ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory")
+ "UpdateDataHistoryVersionComment" = @("Read","ReadDataHistory")
+ "View" = @("Read")
+ "ViewDataHistory" = @("Read","ReadDataHistory","View")
+}
+
+$script:rightDepsByType = @{
+ "ChartOfAccounts" = @{
+ "ReadDataHistory" = @()
+ "ReadDataHistoryOfMissingData" = @("ReadDataHistory")
+ "UpdateDataHistory" = @("ReadDataHistory")
+ "UpdateDataHistoryOfMissingData" = @("ReadDataHistory","ReadDataHistoryOfMissingData","UpdateDataHistory")
+ "UpdateDataHistoryVersionComment" = @("ReadDataHistory")
+ }
+ "DataProcessor" = @{
+ "View" = @("Use")
+ }
+ "InformationRegister" = @{
+ "UpdateDataHistoryOfMissingData" = @("Read","ReadDataHistory","UpdateDataHistory")
+ }
+ "Report" = @{
+ "View" = @("Use")
+ }
+}
+
+$script:configurationLegacyDeps = @("AnalyticsSystemClient","MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk","MainWindowModeNormal","MainWindowModeWorkplace")
+
+# Права конфигурации: до формата 2.19 платформа взводила весь блок режимов окна вместе с
+# любым правом, с 2.19 (8.3.26) перестала. Сами права допустимы и там, и там.
+$script:configurationLegacyRank = 218
+
+# Замыкание набора прав объекта. Возвращает @{ Rights = <итог>; Added = <что дописано> }.
+function Close-RightsDependencies {
+ param([string]$objName, $rights, [int]$formatRank)
+ $parts = $objName -split '\.'
+ # У вложенных объектов (реквизит, ТЧ, измерение) зависимостей нет — платформа их не трогает.
+ if ($parts.Count -ge 3) { return @{ Rights = $rights; Added = @() } }
+ $objectType = $parts[0]
+ $allowed = $script:knownRights[$objectType]
+ if (-not $allowed) { return @{ Rights = $rights; Added = @() } }
+ $have = [ordered]@{}
+ foreach ($r in $rights) { if (-not $have.Contains($r.Name)) { $have[$r.Name] = $r } }
+ $byType = $script:rightDepsByType[$objectType]
+ $added = @()
+ $queue = @($have.Keys)
+ while ($queue.Count -gt 0) {
+ $name = $queue[0]
+ $queue = @($queue | Select-Object -Skip 1)
+ $need = if ($byType -and $byType.Contains($name)) { $byType[$name] } else { $script:rightDeps[$name] }
+ if (-not $need) { continue }
+ foreach ($dep in $need) {
+ if ($have.Contains($dep)) { continue }
+ if ($allowed -notcontains $dep) { continue }
+ $have[$dep] = @{ Name = $dep; Value = "true"; Condition = $null }
+ $added += $dep
+ $queue += $dep
+ }
+ }
+ if ($objectType -eq 'Configuration' -and $formatRank -le $script:configurationLegacyRank -and $have.Count -gt 0) {
+ foreach ($dep in $script:configurationLegacyDeps) {
+ if ($have.Contains($dep)) { continue }
+ $have[$dep] = @{ Name = $dep; Value = "true"; Condition = $null }
+ $added += $dep
+ }
+ }
+ $result = @()
+ foreach ($k in $have.Keys) { $result += ,$have[$k] }
+ return @{ Rights = $result; Added = $added }
+}
+
# --- 5. Helpers ---
function Get-ObjectType {
@@ -1332,6 +1438,15 @@ X "`t$sfno"
X "`t$sfab"
X "`t$irco"
+# Замыкание зависимостей: платформа при загрузке всё равно доведёт набор до полного,
+# и файл разошёлся бы с базой. Дописанное показываем — права выдаются не молча.
+$closureNotes = @()
+foreach ($o in $parsedObjects) {
+ $closed = Close-RightsDependencies -objName $o.Name -rights $o.Rights -formatRank (Get-FormatRank $formatVersion)
+ $o.Rights = @($closed.Rights)
+ if ($closed.Added.Count -gt 0) { $closureNotes += " $($o.Name): по зависимости добавлено — $($closed.Added -join ', ')" }
+}
+
# Порядок как у платформы: узлы по uuid объекта, права — по канону типа. Иначе первая же
# выгрузка из Конфигуратора переставит их и даст диф, которого никто не делал.
$parsedObjects = @(Sort-ObjectsByUuid -objects $parsedObjects -configRoot $resolvedOutputDir)
@@ -1606,6 +1721,7 @@ Write-Host " UUID: $uuid"
Write-Host " Metadata: $metadataPath"
Write-Host " Rights: $rightsPath"
Write-Host " Objects: $($parsedObjects.Count), Rights: $totalRights, Templates: $templateCount"
+foreach ($note in $closureNotes) { Write-Host $note }
switch ($regResult) {
"added" { Write-Host " Configuration.xml: $roleName added to ChildObjects" }
"already" { Write-Host " Configuration.xml: $roleName already registered" }
diff --git a/.claude/skills/role-compile/scripts/role-compile.py b/.claude/skills/role-compile/scripts/role-compile.py
index 814905af5..ad9917ea5 100644
--- a/.claude/skills/role-compile/scripts/role-compile.py
+++ b/.claude/skills/role-compile/scripts/role-compile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# role-compile v1.38 — Compile 1C role from JSON
+# role-compile v1.39 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -771,6 +771,110 @@ def get_nested_rights(object_type, kind):
return NESTED_KIND_RIGHTS.get(kind)
+# --- Зависимости прав (замерено на платформе) ---
+# Платформа при загрузке сама доводит набор до замыкания: выдал Edit — получил ещё
+# Read, Update и View. Пишем замыкание сразу, иначе файл и база расходятся.
+# Таблица общая для типов; исключения — там, где у типа своя механика (обработка и отчёт
+# держатся на Use, план счетов не тянет Read под историю данных).
+RIGHT_DEPS = {
+ "Delete": ["Read"],
+ "Edit": ["Read", "Update", "View"],
+ "EditDataHistoryVersionComment": ["Read", "ReadDataHistory", "UpdateDataHistoryVersionComment", "View"],
+ "Execute": ["Read", "Update"],
+ "InputByString": ["Read", "View"],
+ "Insert": ["Read"],
+ "InteractiveActivate": ["Read", "Update"],
+ "InteractiveChangeOfPosted": ["Edit", "Read", "Update", "View"],
+ "InteractiveClearDeletionMark": ["Edit", "Read", "Update", "View"],
+ "InteractiveClearDeletionMarkPredefinedData": ["Edit", "InteractiveClearDeletionMark", "Read", "Update", "View"],
+ "InteractiveDelete": ["Delete", "Edit", "Read", "Update", "View"],
+ "InteractiveDeleteMarked": ["Delete", "Edit", "Read", "Update", "View"],
+ "InteractiveDeleteMarkedPredefinedData": ["Delete", "Edit", "InteractiveDeleteMarked", "Read", "Update", "View"],
+ "InteractiveDeletePredefinedData": ["Delete", "Edit", "InteractiveDelete", "Read", "Update", "View"],
+ "InteractiveExecute": ["Execute", "Read", "Update"],
+ "InteractiveInsert": ["Edit", "Insert", "Read", "Update", "View"],
+ "InteractivePosting": ["Edit", "Posting", "Read", "Update", "View"],
+ "InteractivePostingRegular": ["Edit", "InteractivePosting", "Posting", "Read", "Update", "View"],
+ "InteractiveSetDeletionMark": ["Edit", "Read", "Update", "View"],
+ "InteractiveSetDeletionMarkPredefinedData": ["Edit", "InteractiveSetDeletionMark", "Read", "Update", "View"],
+ "InteractiveStart": ["Read", "Start", "Update"],
+ "InteractiveUndoPosting": ["Edit", "Read", "UndoPosting", "Update", "View"],
+ "Posting": ["Read", "Update"],
+ "ReadDataHistory": ["Read"],
+ "ReadDataHistoryOfMissingData": ["Read", "ReadDataHistory"],
+ "Start": ["Read", "Update"],
+ "SwitchToDataHistoryVersion": ["Read", "View"],
+ "UndoPosting": ["Read", "Update"],
+ "Update": ["Read"],
+ "UpdateDataHistory": ["Read", "ReadDataHistory"],
+ "UpdateDataHistoryOfMissingData": ["Read", "ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory"],
+ "UpdateDataHistoryVersionComment": ["Read", "ReadDataHistory"],
+ "View": ["Read"],
+ "ViewDataHistory": ["Read", "ReadDataHistory", "View"],
+}
+
+RIGHT_DEPS_BY_TYPE = {
+ "ChartOfAccounts": {
+ "ReadDataHistory": [],
+ "ReadDataHistoryOfMissingData": ["ReadDataHistory"],
+ "UpdateDataHistory": ["ReadDataHistory"],
+ "UpdateDataHistoryOfMissingData": ["ReadDataHistory", "ReadDataHistoryOfMissingData", "UpdateDataHistory"],
+ "UpdateDataHistoryVersionComment": ["ReadDataHistory"],
+ },
+ "DataProcessor": {
+ "View": ["Use"],
+ },
+ "InformationRegister": {
+ "UpdateDataHistoryOfMissingData": ["Read", "ReadDataHistory", "UpdateDataHistory"],
+ },
+ "Report": {
+ "View": ["Use"],
+ },
+}
+
+CONFIGURATION_LEGACY_DEPS = ["AnalyticsSystemClient", "MainWindowModeEmbeddedWorkplace", "MainWindowModeFullscreenWorkplace", "MainWindowModeKiosk", "MainWindowModeNormal", "MainWindowModeWorkplace"]
+
+# Права конфигурации: до формата 2.19 платформа взводила весь блок режимов окна вместе с
+# любым правом, с 2.19 (8.3.26) перестала. Сами права допустимы и там, и там.
+CONFIGURATION_LEGACY_RANK = 218
+
+
+def close_rights_dependencies(object_name, rights, format_rank):
+ """Замыкание набора прав объекта. Возвращает (итоговые права, что дописано)."""
+ parts = object_name.split('.')
+ # У вложенных объектов (реквизит, ТЧ, измерение) зависимостей нет — платформа их не трогает.
+ if len(parts) >= 3:
+ return rights, []
+ object_type = parts[0]
+ allowed = KNOWN_RIGHTS.get(object_type)
+ if not allowed:
+ return rights, []
+ have = {}
+ for r in rights:
+ have.setdefault(r['Name'], r)
+ by_type = RIGHT_DEPS_BY_TYPE.get(object_type, {})
+ added = []
+ queue = list(have.keys())
+ while queue:
+ name = queue.pop(0)
+ need = by_type[name] if name in by_type else RIGHT_DEPS.get(name)
+ if not need:
+ continue
+ for dep in need:
+ if dep in have or dep not in allowed:
+ continue
+ have[dep] = {'Name': dep, 'Value': 'true', 'Condition': None}
+ added.append(dep)
+ queue.append(dep)
+ if object_type == 'Configuration' and format_rank <= CONFIGURATION_LEGACY_RANK and have:
+ for dep in CONFIGURATION_LEGACY_DEPS:
+ if dep in have:
+ continue
+ have[dep] = {'Name': dep, 'Value': 'true', 'Condition': None}
+ added.append(dep)
+ return list(have.values()), added
+
+
# --- Канонический порядок прав и узлов (замерено на платформе) ---
# Платформа нормализует порядок внутри
+
+ View
+ true
+
UpdateDataHistorySettings
true
@@ -24,10 +28,34 @@
Read
true
+
+ Update
+ true
+
+
+ Delete
+ true
+
+
+ View
+ true
+
+
+ Edit
+ true
+
InteractiveDeleteMarked
true
+
+ ReadDataHistory
+ true
+
+
+ UpdateDataHistoryVersionComment
+ true
+
EditDataHistoryVersionComment
true
@@ -58,6 +86,22 @@
Read
true
+
+ Update
+ true
+
+
+ Delete
+ true
+
+
+ View
+ true
+
+
+ Edit
+ true
+
InteractiveDeleteMarked
true
diff --git a/tests/skills/cases/role-compile/snapshots/type-aliases-yo/Roles/РольАлиасы/Ext/Rights.xml b/tests/skills/cases/role-compile/snapshots/type-aliases-yo/Roles/РольАлиасы/Ext/Rights.xml
index a258916bb..aa0a781b9 100644
--- a/tests/skills/cases/role-compile/snapshots/type-aliases-yo/Roles/РольАлиасы/Ext/Rights.xml
+++ b/tests/skills/cases/role-compile/snapshots/type-aliases-yo/Roles/РольАлиасы/Ext/Rights.xml
@@ -5,6 +5,10 @@
false
Report.Сводный
+
+ Use
+ true
+
View
true