mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-18 15:45:52 +03:00
feat(role-compile): набор прав замыкается по зависимостям
Платформа при загрузке сама доводит набор до полного: выдал Edit — получил ещё Read, Update и View, выдал View у обработки — получил Use. Навык писал ровно заданное, поэтому файл роли и база расходились сразу после первой загрузки. Таблица снята с платформы: по одной роли на каждое право, 209 + 43 роли в двух прогонах, ни одного отвергнутого (debug/role-edit). Зависимости оказались общими для типов, исключений четыре — обработка и отчёт держатся на Use, план счетов не тянет Read под историю данных, регистр сведений — короче на одно звено. Права, недопустимые для типа, отсекаются, у вложенных объектов зависимостей нет. Права конфигурации версионные: до формата 2.19 платформа взводила весь блок режимов окна с любым правом, с 2.19 (8.3.26) перестала. Граница измерена на шести платформах, 8.3.20 … 8.5.1. Дописанное перечисляется в выводе — права выдаются не молча. Проверка: выход обоих портов после загрузки и выгрузки совпал байт в байт. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
This commit is contained in:
co-authored by
Claude Opus 5
parent
bbc3be55a5
commit
ce37fe6e53
@@ -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, операции, канале. Права на сервис целиком не существует.
|
||||
|
||||
@@ -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<setForNewObjects>$sfno</setForNewObjects>"
|
||||
X "`t<setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
||||
X "`t<independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
||||
|
||||
# Замыкание зависимостей: платформа при загрузке всё равно доведёт набор до полного,
|
||||
# и файл разошёлся бы с базой. Дописанное показываем — права выдаются не молча.
|
||||
$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: <Role>$roleName</Role> added to ChildObjects" }
|
||||
"already" { Write-Host " Configuration.xml: <Role>$roleName</Role> already registered" }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
# --- Канонический порядок прав и узлов (замерено на платформе) ---
|
||||
# Платформа нормализует порядок <right> внутри <object> и порядок самих <object>:
|
||||
# права идут в фиксированном для типа порядке, узлы — по uuid объекта метаданных.
|
||||
@@ -1583,6 +1687,14 @@ def main():
|
||||
lines.append(f'\t<setForAttributesByDefault>{sfab}</setForAttributesByDefault>')
|
||||
lines.append(f'\t<independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>')
|
||||
|
||||
# Замыкание зависимостей: платформа при загрузке всё равно доведёт набор до полного,
|
||||
# и файл разошёлся бы с базой. Дописанное показываем — права выдаются не молча.
|
||||
closure_notes = []
|
||||
for o in parsed_objects:
|
||||
o['Rights'], added = close_rights_dependencies(o['Name'], o['Rights'], format_rank(format_version))
|
||||
if added:
|
||||
closure_notes.append(f" {o['Name']}: по зависимости добавлено — {', '.join(added)}")
|
||||
|
||||
# Порядок как у платформы: узлы по uuid объекта, права — по канону типа. Иначе первая же
|
||||
# выгрузка из Конфигуратора переставит их и даст диф, которого никто не делал.
|
||||
parsed_objects = sort_objects_by_uuid(parsed_objects, out_dir_resolved)
|
||||
@@ -1658,6 +1770,8 @@ def main():
|
||||
print(f" Metadata: {metadata_path}")
|
||||
print(f" Rights: {rights_path}")
|
||||
print(f" Objects: {len(parsed_objects)}, Rights: {total_rights}, Templates: {template_count}")
|
||||
for note in closure_notes:
|
||||
print(note)
|
||||
if reg_result == 'added':
|
||||
print(f" Configuration.xml: <Role>{role_name}</Role> added to ChildObjects")
|
||||
elif reg_result == 'already':
|
||||
|
||||
Reference in New Issue
Block a user