diff --git a/.claude/skills/role-compile/SKILL.md b/.claude/skills/role-compile/SKILL.md index 5df23c48..27b62771 100644 --- a/.claude/skills/role-compile/SKILL.md +++ b/.claude/skills/role-compile/SKILL.md @@ -1,7 +1,7 @@ --- name: role-compile description: Создание роли 1С — метаданные и Rights.xml из описания прав -argument-hint: +argument-hint: allowed-tools: - Bash - Read @@ -9,199 +9,331 @@ allowed-tools: - Glob --- -# /role-compile — создание роли 1С +# /role-compile — генерация роли 1С из JSON DSL -Создаёт файлы роли (метаданные + Rights.xml) по описанию прав. Скрипта нет — агент генерирует XML по шаблонам ниже. +Принимает компактное JSON-определение роли и генерирует два файла: метаданные (`Roles/Имя.xml`) и права (`Roles/Имя/Ext/Rights.xml`). UUID генерируется автоматически. ## Использование ``` -/role-compile +/role-compile ``` -- **RoleName** — программное имя роли -- **RolesDir** — каталог `Roles/` в исходниках конфигурации +## Параметры -## Файловая структура и регистрация +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| JsonPath | да | Путь к JSON-определению роли | +| RolesDir | да | Каталог `Roles/` в исходниках конфигурации | + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude\skills\role-compile\scripts\role-compile.ps1 -JsonPath "" -OutputDir "" +``` + +## Выходные файлы ``` -Roles/ - ИмяРоли.xml ← метаданные (uuid, имя, синоним) +RolesDir/ + ИмяРоли.xml ← метаданные (uuid, имя, синоним) ИмяРоли/ Ext/ - Rights.xml ← определение прав + Rights.xml ← определение прав ``` -В `Configuration.xml` добавить `ИмяРоли` в секцию ``. +После генерации: добавить `ИмяРоли` в `` файла `Configuration.xml`. -## Шаблон метаданных: Roles/ИмяРоли.xml +## JSON DSL — справка -```xml - - - - - ИмяРоли - - - ru - Отображаемое имя роли - - - - - - +### Структура верхнего уровня + +```json +{ + "name": "ИмяРоли", + "synonym": "Отображаемое имя роли", + "comment": "", + "setForNewObjects": false, + "setForAttributesByDefault": true, + "independentRightsOfChildObjects": false, + "objects": [ ... ], + "templates": [ ... ] +} ``` -**UUID:** `powershell.exe -Command "[guid]::NewGuid().ToString()"` +- `name` — программное имя роли (обязательно) +- `synonym` — отображаемое имя (по умолчанию = name) +- `comment` — комментарий (по умолчанию пусто) +- Глобальные флаги — по умолчанию `false`, `true`, `false` -## Шаблон прав: Roles/ИмяРоли/Ext/Rights.xml +### Объекты: два формата -```xml - - - false - true - false - - -``` +Массив `objects` принимает строки (shorthand) и объекты (полная форма). -NB: namespace `http://v8.1c.ru/8.2/roles` (исторически 8.2, не 8.3). - -## Формат блока прав - -```xml - - Catalog.Номенклатура - Readtrue - Viewtrue - -``` - -Имя объекта — dot-нотация: `ТипОбъекта.Имя[.ТипВложенного.ИмяВложенного]`. - -## Практические наборы прав - -### Catalog / ExchangePlan - -| Набор | Права | -|-------|-------| -| Чтение | Read, View, InputByString | -| Полные | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark | - -### Document - -| Набор | Права | -|-------|-------| -| Чтение | Read, View, InputByString | -| Полные | Read, Insert, Update, Delete, View, Edit, InputByString, Posting, UndoPosting, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted | - -### InformationRegister / AccumulationRegister / AccountingRegister - -| Набор | Права | -|-------|-------| -| Чтение | Read, View | -| Полные | Read, Update, View, Edit | - -TotalsControl — только для управления итогами, обычно не нужно. - -### Простые типы - -| Тип | Права | -|-----|-------| -| `DataProcessor` / `Report` | Use, View | -| `Constant` | Read, Update, View, Edit (чтение: Read, View) | -| `CommonForm` / `CommonCommand` / `Subsystem` / `FilterCriterion` | View | -| `DocumentJournal` | Read, View | -| `Sequence` | Read, Update | -| `SessionParameter` | Get (+ Set если пишет) | -| `CommonAttribute` | View (+ Edit если редактирует) | -| `WebService` / `HTTPService` / `IntegrationService` | Use | -| `CalculationRegister` | Read, View | - -### Редкие ссылочные типы - -| Тип | Особенности (относительно Catalog) | -|-----|-------| -| `ChartOfAccounts`, `ChartOfCharacteristicTypes`, `ChartOfCalculationTypes` | + Predefined-права (InteractiveDeletePredefinedData и др.) | -| `BusinessProcess` | + Start, InteractiveStart, InteractiveActivate | -| `Task` | + Execute, InteractiveExecute, InteractiveActivate | - -### Типы БЕЗ прав в ролях - -Enum, FunctionalOption, DefinedType, CommonModule, CommonPicture, CommonTemplate — не фигурируют в Rights.xml. - -### Вложенные объекты (права: View, Edit) +#### Строковый shorthand ``` -Catalog.Контрагенты.Attribute.ИНН -Document.Реализация.StandardAttribute.Posted -Document.Реализация.TabularSection.Товары -InformationRegister.Цены.Dimension.Номенклатура -InformationRegister.Цены.Resource.Цена -Catalog.Контрагенты.Command.ОткрытьКарточку ← только View -Task.Задача.AddressingAttribute.Исполнитель +"ОбъектМетаданных: @пресет" +"ОбъектМетаданных: Право1, Право2" ``` -Используются для точечного запрета: `false` на конкретный реквизит. - -### Configuration - -Объект: `Configuration.ИмяКонфигурации`. Ключевые права: Administration, DataAdministration, ThinClient, WebClient, ThickClient, MobileClient, ExternalConnection, Output, SaveUserData, InteractiveOpenExtDataProcessors, InteractiveOpenExtReports, MainWindowModeNormal, MainWindowModeWorkplace, MainWindowModeEmbeddedWorkplace, MainWindowModeFullscreenWorkplace, MainWindowModeKiosk, AnalyticsSystemClient. - -> DataHistory-права (ReadDataHistory, UpdateDataHistory и др.) существуют у Catalog, Document, Register, Constant — но используются крайне редко, в типовых ролях практически не встречаются. - -## RLS (ограничения на уровне записей) - -Внутрь ``, после ``. Применяется к Read, Update, Insert, Delete. - -```xml - - Read - true - - #ИмяШаблона("Параметр1", "Параметр2") - - +Примеры: +```json +"objects": [ + "Catalog.Номенклатура: @view", + "Document.Реализация: @edit", + "InformationRegister.Цены: Read, Update", + "DataProcessor.Загрузка: @use" +] ``` -Шаблоны — в конце Rights.xml, после всех ``: +#### Объектная форма (для RLS и переопределений) -```xml - - ИмяШаблона(Параметр1, Параметр2) - Текст шаблона - +```json +{ + "name": "Document.Реализация", + "preset": "view", + "rights": { "Delete": false }, + "rls": { "Read": "#ДляОбъекта(\"\")" } +} ``` -`&` в условии → `&`. Типичные шаблоны: ДляОбъекта, ПоЗначениям, ДляРегистра. +- `preset` — базовый набор прав (`"view"`, `"edit"`, `"use"`) +- `rights` — переопределения: dict `{"Right": true/false}` или массив `["Right1", "Right2"]` +- `rls` — RLS-ограничения: `{"ИмяПрава": "текст условия"}` -## Пример: роль для регламентного задания +### Пресеты (`@view`, `@edit`, `@use`) -```xml - - Catalog.Валюты - Readtrue - - - InformationRegister.КурсыВалют - Readtrue - Updatetrue - - - Constant.ОсновнаяВалюта - Readtrue - +Пресеты обозначаются `@` в строковом формате. В объектной форме ключ `preset` без `@`. + +#### `@view` — просмотр + +| Тип объекта | Права | +|-------------|-------| +| Catalog, ExchangePlan, Document, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task | Read, View, InputByString | +| InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, Constant, DocumentJournal | Read, View | +| Sequence | Read | +| CommonForm, CommonCommand, Subsystem, FilterCriterion, CommonAttribute | View | +| SessionParameter | Get | +| Configuration | ThinClient, WebClient, Output, SaveUserData, MainWindowModeNormal | + +#### `@edit` — полное редактирование + +| Тип объекта | Права | +|-------------|-------| +| Catalog, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark | +| Document | Read, Insert, Update, Delete, View, Edit, InputByString, Posting, UndoPosting, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted | +| BusinessProcess | Read, Insert, Update, Delete, View, Edit, InputByString, Start, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveActivate, InteractiveStart | +| Task | Read, Insert, Update, Delete, View, Edit, InputByString, Execute, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveActivate, InteractiveExecute | +| InformationRegister, AccumulationRegister, AccountingRegister, Constant | Read, Update, View, Edit | +| DocumentJournal | Read, View | +| Sequence | Read, Update | +| SessionParameter | Get, Set | +| CommonAttribute | View, Edit | + +#### `@use` — использование + +| Тип объекта | Права | +|-------------|-------| +| DataProcessor, Report | Use, View | +| CommonForm, CommonCommand, Subsystem | View | +| WebService, HTTPService, IntegrationService | Use | + +Если пресет не определён для типа объекта — предупреждение с подсказкой доступных. + +### Русские синонимы + +Скрипт автоматически транслирует русские имена в английские. Можно смешивать: `"Справочник.Контрагенты: Чтение, View"` — работает. + +**Типы объектов:** + +| Русский | English | +|---------|---------| +| `Справочник` | Catalog | +| `Документ` | Document | +| `РегистрСведений` | InformationRegister | +| `РегистрНакопления` | AccumulationRegister | +| `РегистрБухгалтерии` | AccountingRegister | +| `РегистрРасчета` | CalculationRegister | +| `Константа` | Constant | +| `ПланСчетов` | ChartOfAccounts | +| `ПланВидовХарактеристик` | ChartOfCharacteristicTypes | +| `ПланВидовРасчета` | ChartOfCalculationTypes | +| `ПланОбмена` | ExchangePlan | +| `БизнесПроцесс` | BusinessProcess | +| `Задача` | Task | +| `Обработка` | DataProcessor | +| `Отчет` | Report | +| `ОбщаяФорма` | CommonForm | +| `ОбщаяКоманда` | CommonCommand | +| `Подсистема` | Subsystem | +| `КритерийОтбора` | FilterCriterion | +| `ЖурналДокументов` | DocumentJournal | +| `Последовательность` | Sequence | +| `ВебСервис` | WebService | +| `HTTPСервис` | HTTPService | +| `СервисИнтеграции` | IntegrationService | +| `ПараметрСеанса` | SessionParameter | +| `ОбщийРеквизит` | CommonAttribute | +| `Конфигурация` | Configuration | +| `Перечисление` | Enum | + +Вложенные типы: `Реквизит` → Attribute, `СтандартныйРеквизит` → StandardAttribute, `ТабличнаяЧасть` → TabularSection, `Измерение` → Dimension, `Ресурс` → Resource, `Команда` → Command, `РеквизитАдресации` → AddressingAttribute. + +**Права (основные):** + +| Русский | English | +|---------|---------| +| `Чтение` | Read | +| `Добавление` | Insert | +| `Изменение` | Update | +| `Удаление` | Delete | +| `Просмотр` | View | +| `Редактирование` | Edit | +| `ВводПоСтроке` | InputByString | +| `Проведение` | Posting | +| `ОтменаПроведения` | UndoPosting | +| `Использование` | Use | +| `Получение` | Get | +| `Установка` | Set | +| `Старт` | Start | +| `Выполнение` | Execute | +| `УправлениеИтогами` | TotalsControl | + +**Права (интерактивные):** + +| Русский | English | +|---------|---------| +| `ИнтерактивноеДобавление` | InteractiveInsert | +| `ИнтерактивнаяПометкаУдаления` | InteractiveSetDeletionMark | +| `ИнтерактивноеСнятиеПометкиУдаления` | InteractiveClearDeletionMark | +| `ИнтерактивноеУдаление` | InteractiveDelete | +| `ИнтерактивноеУдалениеПомеченных` | InteractiveDeleteMarked | +| `ИнтерактивноеПроведение` | InteractivePosting | +| `ИнтерактивноеПроведениеНеоперативное` | InteractivePostingRegular | +| `ИнтерактивнаяОтменаПроведения` | InteractiveUndoPosting | +| `ИнтерактивноеИзменениеПроведенных` | InteractiveChangeOfPosted | +| `ИнтерактивныйСтарт` | InteractiveStart | +| `ИнтерактивнаяАктивация` | InteractiveActivate | +| `ИнтерактивноеВыполнение` | InteractiveExecute | + +**Права (конфигурация):** + +| Русский | English | +|---------|---------| +| `Администрирование` | Administration | +| `АдминистрированиеДанных` | DataAdministration | +| `ТонкийКлиент` | ThinClient | +| `ТолстыйКлиент` | ThickClient | +| `ВебКлиент` | WebClient | +| `МобильныйКлиент` | MobileClient | +| `ВнешнееСоединение` | ExternalConnection | +| `Вывод` | Output | +| `СохранениеДанныхПользователя` | SaveUserData | + +### Шаблоны ограничений (RLS templates) + +```json +"templates": [ + { + "name": "ДляОбъекта(Модификатор)", + "condition": "// текст шаблона\nГДЕ 1=1\n&Модификатор" + } +] ``` -Фоновые задания не требуют Interactive/View/Edit-прав и прав конфигурации (ThinClient, WebClient и др.) — только программные (Read, Insert, Update, Delete, Posting). +`&` в условии автоматически экранируется в `&` в XML. + +## Примеры + +### Простая роль (только пресеты) + +```json +{ + "name": "ЧтениеНоменклатуры", + "synonym": "Чтение номенклатуры", + "objects": [ + "Catalog.Номенклатура: @view", + "Catalog.Контрагенты: @view", + "DataProcessor.Загрузка: @use" + ] +} +``` + +### Роль для регламентного задания + +```json +{ + "name": "ОбновлениеЦен", + "synonym": "Обновление цен номенклатуры", + "objects": [ + "Catalog.Номенклатура: Read", + "Catalog.Валюты: Read", + "InformationRegister.ЦеныНоменклатуры: Read, Update", + "Constant.ОсновнаяВалюта: Read" + ] +} +``` + +### Роль с RLS + +```json +{ + "name": "ЧтениеДокументовПоОрганизации", + "synonym": "Чтение документов (ограничение по организации)", + "objects": [ + "Catalog.Организации: @view", + { + "name": "Document.РеализацияТоваровУслуг", + "preset": "view", + "rls": { + "Read": "#ДляОбъекта(\"\")" + } + } + ], + "templates": [ + { + "name": "ДляОбъекта(Модификатор)", + "condition": "ГДЕ Организация = &ТекущаяОрганизация" + } + ] +} +``` + +### Роль с русскими синонимами + +```json +{ + "name": "ПросмотрДанных", + "synonym": "Просмотр данных", + "objects": [ + "Справочник.Контрагенты: @view", + "Документ.Реализация: Чтение, Просмотр", + "РегистрСведений.Цены: @edit", + "Обработка.ЗагрузкаДанных: @use" + ] +} +``` + +### Роль с переопределением прав из пресета + +```json +{ + "name": "ОграниченноеРедактирование", + "synonym": "Редактирование без удаления", + "objects": [ + { + "name": "Catalog.Контрагенты", + "preset": "edit", + "rights": { "Delete": false } + } + ] +} +``` + +## Верификация + +``` +/role-validate [MetadataPath] — проверка корректности XML, прав, RLS +/role-info — визуальная сводка структуры +``` diff --git a/.claude/skills/role-compile/scripts/role-compile.ps1 b/.claude/skills/role-compile/scripts/role-compile.ps1 new file mode 100644 index 00000000..8583ddcb --- /dev/null +++ b/.claude/skills/role-compile/scripts/role-compile.ps1 @@ -0,0 +1,645 @@ +param( + [Parameter(Mandatory)] + [string]$JsonPath, + + [Parameter(Mandatory)] + [string]$OutputDir +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- 1. Load and validate JSON --- + +if (-not (Test-Path $JsonPath)) { + Write-Error "File not found: $JsonPath" + exit 1 +} + +$json = Get-Content -Raw -Encoding UTF8 $JsonPath +$def = $json | ConvertFrom-Json + +if (-not $def.name) { + Write-Error "JSON must have 'name' field (role programmatic name)" + exit 1 +} + +$roleName = "$($def.name)" +$synonym = if ($def.synonym) { "$($def.synonym)" } else { $roleName } +$comment = if ($def.comment) { "$($def.comment)" } else { "" } + +# --- 2. XML helpers --- + +$script:xmlBuf = $null + +function X { + param([string]$text) + $script:xmlBuf.AppendLine($text) | Out-Null +} + +function Esc-Xml { + param([string]$s) + return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') +} + +# --- 3. Russian synonyms → canonical English names --- + +$script:typeAliases = @{ + "Справочник" = "Catalog" + "Документ" = "Document" + "РегистрСведений" = "InformationRegister" + "РегистрНакопления" = "AccumulationRegister" + "РегистрБухгалтерии" = "AccountingRegister" + "РегистрРасчета" = "CalculationRegister" + "Константа" = "Constant" + "ПланСчетов" = "ChartOfAccounts" + "ПланВидовХарактеристик" = "ChartOfCharacteristicTypes" + "ПланВидовРасчета" = "ChartOfCalculationTypes" + "ПланОбмена" = "ExchangePlan" + "БизнесПроцесс" = "BusinessProcess" + "Задача" = "Task" + "Обработка" = "DataProcessor" + "Отчет" = "Report" + "ОбщаяФорма" = "CommonForm" + "ОбщаяКоманда" = "CommonCommand" + "Подсистема" = "Subsystem" + "КритерийОтбора" = "FilterCriterion" + "ЖурналДокументов" = "DocumentJournal" + "Последовательность" = "Sequence" + "ВебСервис" = "WebService" + "HTTPСервис" = "HTTPService" + "СервисИнтеграции" = "IntegrationService" + "ПараметрСеанса" = "SessionParameter" + "ОбщийРеквизит" = "CommonAttribute" + "Конфигурация" = "Configuration" + "Перечисление" = "Enum" + # Nested + "Реквизит" = "Attribute" + "СтандартныйРеквизит" = "StandardAttribute" + "ТабличнаяЧасть" = "TabularSection" + "Измерение" = "Dimension" + "Ресурс" = "Resource" + "Команда" = "Command" + "РеквизитАдресации" = "AddressingAttribute" +} + +$script:rightAliases = @{ + "Чтение" = "Read" + "Добавление" = "Insert" + "Изменение" = "Update" + "Удаление" = "Delete" + "Просмотр" = "View" + "Редактирование" = "Edit" + "ВводПоСтроке" = "InputByString" + "Проведение" = "Posting" + "ОтменаПроведения" = "UndoPosting" + "ИнтерактивноеДобавление" = "InteractiveInsert" + "ИнтерактивнаяПометкаУдаления" = "InteractiveSetDeletionMark" + "ИнтерактивноеСнятиеПометкиУдаления" = "InteractiveClearDeletionMark" + "ИнтерактивноеУдаление" = "InteractiveDelete" + "ИнтерактивноеУдалениеПомеченных" = "InteractiveDeleteMarked" + "ИнтерактивноеПроведение" = "InteractivePosting" + "ИнтерактивноеПроведениеНеоперативное" = "InteractivePostingRegular" + "ИнтерактивнаяОтменаПроведения" = "InteractiveUndoPosting" + "ИнтерактивноеИзменениеПроведенных" = "InteractiveChangeOfPosted" + "Использование" = "Use" + "Получение" = "Get" + "Установка" = "Set" + "Старт" = "Start" + "ИнтерактивныйСтарт" = "InteractiveStart" + "ИнтерактивнаяАктивация" = "InteractiveActivate" + "Выполнение" = "Execute" + "ИнтерактивноеВыполнение" = "InteractiveExecute" + "УправлениеИтогами" = "TotalsControl" + "Администрирование" = "Administration" + "АдминистрированиеДанных" = "DataAdministration" + "ТонкийКлиент" = "ThinClient" + "ВебКлиент" = "WebClient" + "ТолстыйКлиент" = "ThickClient" + "ВнешнееСоединение" = "ExternalConnection" + "Вывод" = "Output" + "СохранениеДанныхПользователя" = "SaveUserData" + "МобильныйКлиент" = "MobileClient" +} + +# Translate Russian object name to English (e.g. "Справочник.Контрагенты" → "Catalog.Контрагенты") +function Translate-ObjectName { + param([string]$name) + $parts = $name.Split(".") + $result = @() + foreach ($p in $parts) { + if ($script:typeAliases.ContainsKey($p)) { + $result += $script:typeAliases[$p] + } else { + $result += $p + } + } + return $result -join "." +} + +# Translate Russian right name to English (e.g. "Чтение" → "Read") +function Translate-RightName { + param([string]$name) + if ($script:rightAliases.ContainsKey($name)) { + return $script:rightAliases[$name] + } + return $name +} + +# --- 4. Known rights per object type (source: docs/1c-role-spec.md) --- + +$script:knownRights = @{ + "Configuration" = @( + "Administration","DataAdministration","UpdateDataBaseConfiguration", + "ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode", + "ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection", + "Automation","Output","SaveUserData","TechnicalSpecialistMode", + "InteractiveOpenExtDataProcessors","InteractiveOpenExtReports", + "AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration", + "MainWindowModeNormal","MainWindowModeWorkplace", + "MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk" + ) + "Catalog" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "Document" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Posting","UndoPosting", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting", + "InteractiveChangeOfPosted", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "InformationRegister" = @( + "Read","Update","View","Edit","TotalsControl", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl") + "AccountingRegister" = @("Read","Update","View","Edit","TotalsControl") + "CalculationRegister" = @("Read","View") + "Constant" = @( + "Read","Update","View","Edit", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "ChartOfAccounts" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ReadDataHistoryOfMissingData", + "UpdateDataHistory","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment" + ) + "ChartOfCharacteristicTypes" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "ChartOfCalculationTypes" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData" + ) + "ExchangePlan" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "BusinessProcess" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveActivate","InteractiveStart" + ) + "Task" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveActivate","InteractiveExecute" + ) + "DataProcessor" = @("Use","View") + "Report" = @("Use","View") + "CommonForm" = @("View") + "CommonCommand" = @("View") + "Subsystem" = @("View") + "FilterCriterion" = @("View") + "DocumentJournal" = @("Read","View") + "Sequence" = @("Read","Update") + "WebService" = @("Use") + "HTTPService" = @("Use") + "IntegrationService" = @("Use") + "SessionParameter" = @("Get","Set") + "CommonAttribute" = @("View","Edit") +} + +# Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute +$script:nestedRights = @("View","Edit") +$script:commandRights = @("View") + +# --- 4. Presets (@view, @edit, @use) --- + +$script:presets = @{ + "view" = @{ + "Catalog" = @("Read","View","InputByString") + "ExchangePlan" = @("Read","View","InputByString") + "Document" = @("Read","View","InputByString") + "ChartOfAccounts" = @("Read","View","InputByString") + "ChartOfCharacteristicTypes" = @("Read","View","InputByString") + "ChartOfCalculationTypes" = @("Read","View","InputByString") + "BusinessProcess" = @("Read","View","InputByString") + "Task" = @("Read","View","InputByString") + "InformationRegister" = @("Read","View") + "AccumulationRegister" = @("Read","View") + "AccountingRegister" = @("Read","View") + "CalculationRegister" = @("Read","View") + "Constant" = @("Read","View") + "DocumentJournal" = @("Read","View") + "Sequence" = @("Read") + "CommonForm" = @("View") + "CommonCommand" = @("View") + "Subsystem" = @("View") + "FilterCriterion" = @("View") + "SessionParameter" = @("Get") + "CommonAttribute" = @("View") + "Configuration" = @("ThinClient","WebClient","Output","SaveUserData","MainWindowModeNormal") + } + "edit" = @{ + "Catalog" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") + "ExchangePlan" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") + "Document" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Posting","UndoPosting","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting","InteractiveChangeOfPosted") + "ChartOfAccounts" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") + "ChartOfCharacteristicTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") + "ChartOfCalculationTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") + "BusinessProcess" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveStart") + "Task" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveExecute") + "InformationRegister" = @("Read","Update","View","Edit") + "AccumulationRegister" = @("Read","Update","View","Edit") + "AccountingRegister" = @("Read","Update","View","Edit") + "Constant" = @("Read","Update","View","Edit") + "DocumentJournal" = @("Read","View") + "Sequence" = @("Read","Update") + "SessionParameter" = @("Get","Set") + "CommonAttribute" = @("View","Edit") + } + "use" = @{ + "DataProcessor" = @("Use","View") + "Report" = @("Use","View") + "CommonForm" = @("View") + "CommonCommand" = @("View") + "Subsystem" = @("View") + "WebService" = @("Use") + "HTTPService" = @("Use") + "IntegrationService" = @("Use") + } +} + +# --- 5. Helpers --- + +function Get-ObjectType { + param([string]$objectName) + $dotIdx = $objectName.IndexOf(".") + if ($dotIdx -lt 0) { return $objectName } + return $objectName.Substring(0, $dotIdx) +} + +function Is-NestedObject { + param([string]$objectName) + return ($objectName.Split(".").Count -ge 3) +} + +function Resolve-Preset { + param([string]$objectType, [string]$presetName) + + $preset = $presetName.TrimStart('@') + + if (-not $script:presets.ContainsKey($preset)) { + Write-Warning "Unknown preset '@$preset'. Known: @view, @edit, @use" + return @() + } + + $typeMap = $script:presets[$preset] + if (-not $typeMap.ContainsKey($objectType)) { + $available = @() + foreach ($k in $script:presets.Keys) { + if ($script:presets[$k].ContainsKey($objectType)) { + $available += "@$k" + } + } + $availStr = if ($available.Count -gt 0) { $available -join ", " } else { "none" } + Write-Warning "Preset '@$preset' not defined for type '$objectType'. Available: $availStr" + return @() + } + + return @($typeMap[$objectType]) +} + +function Validate-RightName { + param([string]$objectName, [string]$rightName) + + $objectType = Get-ObjectType $objectName + + if (Is-NestedObject $objectName) { + if ($objectName -match '\.Command\.') { + if ($rightName -notin $script:commandRights) { + Write-Warning "${objectName}: '$rightName' not valid for commands (only: View)" + return $false + } + } else { + if ($rightName -notin $script:nestedRights) { + Write-Warning "${objectName}: '$rightName' not valid for nested objects (only: View, Edit)" + return $false + } + } + return $true + } + + if (-not $script:knownRights.ContainsKey($objectType)) { + Write-Warning "${objectName}: unknown object type '$objectType'" + return $true + } + + $validRights = $script:knownRights[$objectType] + if ($rightName -notin $validRights) { + $suggestions = @($validRights | Where-Object { + $_ -like "*$rightName*" -or $rightName -like "*$_*" + }) + $sugStr = if ($suggestions.Count -gt 0) { " Did you mean: $($suggestions -join ', ')?" } else { "" } + Write-Warning "${objectName}: unknown right '$rightName'.$sugStr" + return $false + } + + return $true +} + +# --- 6. Parse object entries --- + +function Parse-ObjectEntry { + param($entry) + + # --- String shorthand --- + if ($entry -is [string]) { + $colonIdx = $entry.IndexOf(':') + if ($colonIdx -lt 0) { + Write-Warning "Invalid string '$entry' -- expected 'Object.Name: @preset' or 'Object.Name: Right1, Right2'" + return $null + } + $objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim()) + $rightsStr = $entry.Substring($colonIdx + 1).Trim() + $objectType = Get-ObjectType $objName + + if ($rightsStr.StartsWith('@')) { + $rightNames = @(Resolve-Preset -objectType $objectType -presetName $rightsStr) + } else { + $rightNames = @($rightsStr -split ',\s*' | ForEach-Object { Translate-RightName $_.Trim() } | Where-Object { $_ }) + foreach ($r in $rightNames) { + Validate-RightName -objectName $objName -rightName $r | Out-Null + } + } + + $rights = @() + foreach ($r in $rightNames) { + $rights += ,@{Name=$r; Value="true"; Condition=$null} + } + return @{ Name = $objName; Rights = $rights } + } + + # --- Object form --- + $objName = Translate-ObjectName "$($entry.name)" + if (-not $objName) { + Write-Warning "Object entry missing 'name' field" + return $null + } + + $objectType = Get-ObjectType $objName + $rightsMap = [ordered]@{} + + # 1) Start with preset + if ($entry.preset) { + $presetRights = @(Resolve-Preset -objectType $objectType -presetName "$($entry.preset)") + foreach ($r in $presetRights) { + $rightsMap[$r] = @{Value="true"; Condition=$null} + } + } + + # 2) Apply explicit rights + if ($entry.rights) { + if ($entry.rights -is [array]) { + foreach ($r in $entry.rights) { + $rName = Translate-RightName "$r" + Validate-RightName -objectName $objName -rightName $rName | Out-Null + $rightsMap[$rName] = @{Value="true"; Condition=$null} + } + } else { + foreach ($p in $entry.rights.PSObject.Properties) { + $rName = Translate-RightName $p.Name + Validate-RightName -objectName $objName -rightName $rName | Out-Null + $boolVal = $p.Value + if ($boolVal -eq $true -or "$boolVal" -eq "True") { + $rightsMap[$rName] = @{Value="true"; Condition=$null} + } else { + $rightsMap[$rName] = @{Value="false"; Condition=$null} + } + } + } + } + + # 3) Apply RLS conditions + if ($entry.rls) { + foreach ($p in $entry.rls.PSObject.Properties) { + $rlsRight = Translate-RightName $p.Name + if ($rightsMap.Contains($rlsRight)) { + $rightsMap[$rlsRight].Condition = "$($p.Value)" + } else { + Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list" + } + } + } + + # Convert to array + $rights = @() + foreach ($k in $rightsMap.Keys) { + $rights += ,@{ + Name = $k + Value = $rightsMap[$k].Value + Condition = $rightsMap[$k].Condition + } + } + + return @{ Name = $objName; Rights = $rights } +} + +# --- 7. Parse all object entries --- + +$parsedObjects = @() +if ($def.objects) { + foreach ($entry in $def.objects) { + $parsed = Parse-ObjectEntry -entry $entry + if ($parsed) { + $parsedObjects += ,$parsed + } + } +} + +# --- 8. Generate UUID --- + +$uuid = [guid]::NewGuid().ToString() + +# --- 9. Emit metadata XML (Roles/Name.xml) --- + +$script:xmlBuf = New-Object System.Text.StringBuilder 4096 + +X '' +X '' +X " " +X ' ' +X " $roleName" +X ' ' +X ' ' +X ' ru' +X " $(Esc-Xml $synonym)" +X ' ' +X ' ' +if ($comment) { + X " $(Esc-Xml $comment)" +} else { + X ' ' +} +X ' ' +X ' ' +X '' + +$metadataXml = $script:xmlBuf.ToString() + +# --- 10. Emit Rights XML (Roles/Name/Ext/Rights.xml) --- + +$script:xmlBuf = New-Object System.Text.StringBuilder 8192 + +X '' +X '' + +# Global flags (defaults match typical 1C roles) +$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" } +$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" } +$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" } + +X " $sfno" +X " $sfab" +X " $irco" + +# Object blocks +$totalRights = 0 +foreach ($obj in $parsedObjects) { + X ' ' + X " $($obj.Name)" + foreach ($right in $obj.Rights) { + X ' ' + X " $($right.Name)" + X " $($right.Value)" + if ($right.Condition) { + X ' ' + X " $(Esc-Xml $right.Condition)" + X ' ' + } + X ' ' + $totalRights++ + } + X ' ' +} + +# RLS restriction templates +$templateCount = 0 +if ($def.templates) { + foreach ($tpl in $def.templates) { + X ' ' + X " $(Esc-Xml "$($tpl.name)")" + X " $(Esc-Xml "$($tpl.condition)")" + X ' ' + $templateCount++ + } +} + +X '' + +$rightsXml = $script:xmlBuf.ToString() + +# --- 11. Write output files --- + +$outDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { + $OutputDir +} else { + Join-Path (Get-Location) $OutputDir +} + +# Metadata: OutputDir/RoleName.xml +$metadataPath = Join-Path $outDir "$roleName.xml" +if (-not (Test-Path $outDir)) { + New-Item -ItemType Directory -Path $outDir -Force | Out-Null +} + +# Rights: OutputDir/RoleName/Ext/Rights.xml +$roleSubDir = Join-Path $outDir $roleName +$extDir = Join-Path $roleSubDir "Ext" +$rightsPath = Join-Path $extDir "Rights.xml" +if (-not (Test-Path $extDir)) { + New-Item -ItemType Directory -Path $extDir -Force | Out-Null +} + +$enc = New-Object System.Text.UTF8Encoding($true) +[System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc) +[System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc) + +# --- 12. Summary --- + +Write-Host "[OK] Role '$roleName' compiled" +Write-Host " UUID: $uuid" +Write-Host " Metadata: $metadataPath" +Write-Host " Rights: $rightsPath" +Write-Host " Objects: $($parsedObjects.Count), Rights: $totalRights, Templates: $templateCount" diff --git a/.claude/skills/role-validate/SKILL.md b/.claude/skills/role-validate/SKILL.md new file mode 100644 index 00000000..03c21583 --- /dev/null +++ b/.claude/skills/role-validate/SKILL.md @@ -0,0 +1,105 @@ +--- +name: role-validate +description: Валидация структурной корректности роли 1С (Rights.xml) — формат, права, RLS, шаблоны +argument-hint: +allowed-tools: + - Bash + - Read +--- + +# /role-validate — валидация роли 1С + +Проверяет корректность `Rights.xml` роли: формат XML, namespace, глобальные флаги, типы объектов, имена прав, RLS-ограничения, шаблоны. Опционально проверяет метаданные роли (UUID, имя, синоним). + +## Использование + +``` +/role-validate [MetadataPath] +``` + +## Запуск скрипта + +```powershell +powershell.exe -NoProfile -File .claude\skills\role-validate\scripts\role-validate.ps1 -RightsPath [-MetadataPath ] [-OutFile ] +``` + +### Параметры + +| Параметр | Обязательный | Описание | +|----------|:------------:|----------| +| `-RightsPath` | да | Путь к `Rights.xml` роли | +| `-MetadataPath` | нет | Путь к метаданным роли (`Roles/ИмяРоли.xml`) | +| `-OutFile` | нет | Записать результат в файл (UTF-8 BOM). Без этого — вывод в консоль | + +**Важно:** Для кириллических путей используй `-OutFile` и читай результат через Read tool. + +## Проверки + +### Rights.xml +1. XML well-formed — парсинг без ошибок +2. Корневой элемент `` с namespace `http://v8.1c.ru/8.2/roles` +3. Три глобальных флага: `setForNewObjects`, `setForAttributesByDefault`, `independentRightsOfChildObjects` +4. Для каждого ``: + - `` не пуст + - Тип объекта распознан (Catalog, Document, InformationRegister и т.д.) + - Каждое `` имеет `` и `` (`true`/`false`) + - Имя права валидно для данного типа объекта (с подсказкой при опечатке) +5. Вложенные объекты (3+ сегмента через `.`): допустимы только View, Edit (или Use для IntegrationServiceChannel) +6. RLS ``: `` не пуст +7. Шаблоны ``: `` и `` не пусты + +### Метаданные (опционально) +- Элемент `` найден +- UUID в корректном формате +- `` не пуст +- `` присутствует + +## Формат вывода + +``` +Validating: Roles/МояРоль/Ext/Rights.xml + OK XML well-formed + OK Root element: with correct namespace + OK 3 global flags present + WARN Document.Реализация: unknown right 'Rea'. Did you mean: Read? + OK 12 objects, 45 rights + OK 2 RLS restrictions + OK 1 templates: ДляОбъекта + + OK Metadata: UUID valid (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + OK Metadata: Name = МояРоль + OK Metadata: Synonym present +--- +Result: 0 error(s), 1 warning(s) +``` + +### Уровни сообщений + +| Маркер | Значение | +|--------|----------| +| `OK` | Проверка пройдена | +| `WARN` | Предупреждение (неизвестный тип объекта, подозрительное имя права) | +| `ERR` | Ошибка (невалидный XML, отсутствие обязательных элементов) | + +Код возврата: `0` — без ошибок, `1` — есть ошибки. + +## Примеры + +### Только Rights.xml + +``` +/role-validate upload/acc_8.3.20/Roles/БазовыеПраваБП/Ext/Rights.xml +``` + +### С проверкой метаданных + +``` +/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml +``` + +### Верификация после /role-compile + +``` +/role-compile role.json Roles/ +/role-validate Roles/МояРоль/Ext/Rights.xml Roles/МояРоль.xml +``` diff --git a/.claude/skills/role-validate/scripts/role-validate.ps1 b/.claude/skills/role-validate/scripts/role-validate.ps1 new file mode 100644 index 00000000..a9eacc44 --- /dev/null +++ b/.claude/skills/role-validate/scripts/role-validate.ps1 @@ -0,0 +1,424 @@ +param( + [Parameter(Mandatory)] + [string]$RightsPath, + + [string]$MetadataPath, + + [string]$OutFile +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- 1. Known rights per object type --- + +$script:knownRights = @{ + "Configuration" = @( + "Administration","DataAdministration","UpdateDataBaseConfiguration", + "ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode", + "ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection", + "Automation","Output","SaveUserData","TechnicalSpecialistMode", + "InteractiveOpenExtDataProcessors","InteractiveOpenExtReports", + "AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration", + "MainWindowModeNormal","MainWindowModeWorkplace", + "MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk" + ) + "Catalog" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "Document" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Posting","UndoPosting", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting", + "InteractiveChangeOfPosted", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "InformationRegister" = @( + "Read","Update","View","Edit","TotalsControl", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl") + "AccountingRegister" = @("Read","Update","View","Edit","TotalsControl") + "CalculationRegister" = @("Read","View") + "Constant" = @( + "Read","Update","View","Edit", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "ChartOfAccounts" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ReadDataHistoryOfMissingData", + "UpdateDataHistory","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment" + ) + "ChartOfCharacteristicTypes" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "ChartOfCalculationTypes" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete", + "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", + "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData" + ) + "ExchangePlan" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveDeleteMarked", + "ReadDataHistory","ViewDataHistory","UpdateDataHistory", + "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", + "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", + "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" + ) + "BusinessProcess" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveActivate","InteractiveStart" + ) + "Task" = @( + "Read","Insert","Update","Delete","View","Edit","InputByString", + "Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", + "InteractiveDelete","InteractiveActivate","InteractiveExecute" + ) + "DataProcessor" = @("Use","View") + "Report" = @("Use","View") + "CommonForm" = @("View") + "CommonCommand" = @("View") + "Subsystem" = @("View") + "FilterCriterion" = @("View") + "DocumentJournal" = @("Read","View") + "Sequence" = @("Read","Update") + "WebService" = @("Use") + "HTTPService" = @("Use") + "IntegrationService" = @("Use") + "SessionParameter" = @("Get","Set") + "CommonAttribute" = @("View","Edit") +} + +$script:nestedRights = @("View","Edit") +$script:channelRights = @("Use") +$script:commandRights = @("View") + +# --- 2. Output helpers --- + +$script:lines = @() +$script:errors = 0 +$script:warnings = 0 + +function Out-OK { + param([string]$msg) + $script:lines += " OK $msg" +} + +function Out-WARN { + param([string]$msg) + $script:warnings++ + $script:lines += " WARN $msg" +} + +function Out-ERR { + param([string]$msg) + $script:errors++ + $script:lines += " ERR $msg" +} + +function Get-ObjectType { + param([string]$name) + $dotIdx = $name.IndexOf(".") + if ($dotIdx -lt 0) { return $name } + return $name.Substring(0, $dotIdx) +} + +function Is-NestedObject { + param([string]$name) + return ($name.Split(".").Count -ge 3) +} + +function Find-Similar { + param([string]$needle, [string[]]$haystack) + $result = @($haystack | Where-Object { + $_ -like "*$needle*" -or $needle -like "*$_*" + }) + if ($result.Count -gt 3) { $result = $result[0..2] } + return $result +} + +# --- 3. Validate Rights.xml --- + +$script:lines += "Validating: $RightsPath" + +if (-not (Test-Path $RightsPath)) { + Out-ERR "File not found: $RightsPath" + $script:lines += "---" + $script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" + $output = $script:lines -join "`n" + if ($OutFile) { + $enc = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($OutFile, $output, $enc) + } else { + Write-Host $output + } + exit 1 +} + +# 3a. Parse XML +try { + [xml]$xml = Get-Content -Path $RightsPath -Encoding UTF8 + Out-OK "XML well-formed" +} catch { + Out-ERR "XML parse error: $($_.Exception.Message)" + $script:lines += "---" + $script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" + $output = $script:lines -join "`n" + if ($OutFile) { + $enc = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($OutFile, $output, $enc) + } else { + Write-Host $output + } + exit 1 +} + +$root = $xml.DocumentElement +$rightsNs = "http://v8.1c.ru/8.2/roles" + +# 3b. Check root element +if ($root.LocalName -ne "Rights") { + Out-ERR "Root element is '$($root.LocalName)', expected 'Rights'" +} elseif ($root.NamespaceURI -ne $rightsNs) { + Out-WARN "Namespace is '$($root.NamespaceURI)', expected '$rightsNs'" +} else { + Out-OK "Root element: with correct namespace" +} + +# 3c. Global flags +$flagNames = @("setForNewObjects","setForAttributesByDefault","independentRightsOfChildObjects") +$flagsFound = 0 +foreach ($fn in $flagNames) { + $node = $root.GetElementsByTagName($fn, $rightsNs) + if ($node.Count -gt 0) { + $val = $node[0].InnerText + if ($val -ne "true" -and $val -ne "false") { + Out-WARN "$fn = '$val' (expected 'true' or 'false')" + } + $flagsFound++ + } else { + Out-WARN "Missing global flag: $fn" + } +} +if ($flagsFound -eq 3) { + Out-OK "3 global flags present" +} + +# 3d. Objects +$objects = $root.GetElementsByTagName("object", $rightsNs) +$objCount = $objects.Count +$rightCount = 0 +$rlsCount = 0 + +foreach ($obj in $objects) { + $objName = "" + foreach ($child in $obj.ChildNodes) { + if ($child.LocalName -eq "name") { + $objName = $child.InnerText + break + } + } + + if (-not $objName) { + Out-ERR "Object without " + continue + } + + $objectType = Get-ObjectType $objName + $isNested = Is-NestedObject $objName + + # Check object type is known + if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) { + Out-WARN "${objName}: unknown object type '$objectType'" + } + + # Check rights + foreach ($child in $obj.ChildNodes) { + if ($child.LocalName -ne "right") { continue } + + $rName = "" + $rValue = "" + $hasRLS = $false + + foreach ($rc in $child.ChildNodes) { + if ($rc.LocalName -eq "name") { $rName = $rc.InnerText } + if ($rc.LocalName -eq "value") { $rValue = $rc.InnerText } + if ($rc.LocalName -eq "restrictionByCondition") { + $hasRLS = $true + $rlsCount++ + # Check condition not empty + $condNode = $null + foreach ($rcc in $rc.ChildNodes) { + if ($rcc.LocalName -eq "condition") { $condNode = $rcc } + } + if (-not $condNode -or -not $condNode.InnerText) { + Out-WARN "${objName}: RLS condition for '$rName' is empty" + } + } + } + + if (-not $rName) { + Out-ERR "${objName}: without " + continue + } + + if ($rValue -ne "true" -and $rValue -ne "false") { + Out-ERR "${objName}: right '$rName' has invalid value '$rValue'" + continue + } + + $rightCount++ + + # Validate right name + if ($isNested) { + if ($objName -match '\.Command\.') { + if ($rName -notin $script:commandRights) { + Out-WARN "${objName}: '$rName' not valid for commands (only: View)" + } + } elseif ($objName -match '\.IntegrationServiceChannel\.') { + if ($rName -notin $script:channelRights) { + Out-WARN "${objName}: '$rName' not valid for channels (only: Use)" + } + } else { + if ($rName -notin $script:nestedRights) { + Out-WARN "${objName}: '$rName' not valid for nested objects (only: View, Edit)" + } + } + } elseif ($script:knownRights.ContainsKey($objectType)) { + $validRights = $script:knownRights[$objectType] + if ($rName -notin $validRights) { + $similar = Find-Similar -needle $rName -haystack $validRights + $sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" } + Out-WARN "${objName}: unknown right '$rName'.$sugStr" + } + } + } +} + +Out-OK "$objCount objects, $rightCount rights" +if ($rlsCount -gt 0) { + Out-OK "$rlsCount RLS restrictions" +} + +# 3e. Templates +$templates = $root.GetElementsByTagName("restrictionTemplate", $rightsNs) +if ($templates.Count -gt 0) { + $tplNames = @() + foreach ($tpl in $templates) { + $tName = "" + $tCond = "" + foreach ($child in $tpl.ChildNodes) { + if ($child.LocalName -eq "name") { $tName = $child.InnerText } + if ($child.LocalName -eq "condition") { $tCond = $child.InnerText } + } + if (-not $tName) { + Out-WARN "Restriction template without " + } else { + $parenIdx = $tName.IndexOf("(") + $shortName = if ($parenIdx -gt 0) { $tName.Substring(0, $parenIdx) } else { $tName } + $tplNames += $shortName + } + if (-not $tCond) { + Out-WARN "Template '$tName': empty " + } + } + Out-OK "$($templates.Count) templates: $($tplNames -join ', ')" +} + +# --- 4. Validate metadata (optional) --- + +if ($MetadataPath) { + $script:lines += "" + + if (-not (Test-Path $MetadataPath)) { + Out-ERR "Metadata file not found: $MetadataPath" + } else { + try { + [xml]$metaXml = Get-Content -Path $MetadataPath -Encoding UTF8 + $roleNode = $metaXml.DocumentElement.SelectSingleNode("//*[local-name()='Role']") + if (-not $roleNode) { + Out-ERR "Metadata: element not found" + } else { + $uuid = $roleNode.GetAttribute("uuid") + if ($uuid -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { + Out-OK "Metadata: UUID valid ($uuid)" + } else { + Out-ERR "Metadata: invalid UUID format '$uuid'" + } + + $nameNode = $roleNode.SelectSingleNode(".//*[local-name()='Name']") + if ($nameNode -and $nameNode.InnerText) { + Out-OK "Metadata: Name = $($nameNode.InnerText)" + } else { + Out-ERR "Metadata: is empty or missing" + } + + $synNode = $roleNode.SelectSingleNode(".//*[local-name()='Synonym']") + if ($synNode -and $synNode.InnerXml) { + Out-OK "Metadata: Synonym present" + } else { + Out-WARN "Metadata: is empty" + } + } + } catch { + Out-ERR "Metadata XML parse error: $($_.Exception.Message)" + } + } +} + +# --- 5. Summary --- + +$script:lines += "---" +$script:lines += "Result: $($script:errors) error(s), $($script:warnings) warning(s)" + +$output = $script:lines -join "`n" + +if ($OutFile) { + $outPath = if ([System.IO.Path]::IsPathRooted($OutFile)) { $OutFile } else { Join-Path (Get-Location) $OutFile } + $outDir = [System.IO.Path]::GetDirectoryName($outPath) + if (-not (Test-Path $outDir)) { + New-Item -ItemType Directory -Path $outDir -Force | Out-Null + } + $enc = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($outPath, $output, $enc) + Write-Host "[OK] Validation result written to: $outPath" +} else { + Write-Host $output +} + +if ($script:errors -gt 0) { exit 1 } else { exit 0 } diff --git a/README.md b/README.md index ef4e042d..306fe29f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ | Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) | | Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) | | Управляемые формы (Form) | 6 навыков `/form-*` | Создание, анализ, генерация, модификация, валидация управляемых форм | [Подробнее](docs/form-guide.md) | -| Роли (Role) | 2 навыка `/role-*` | Анализ прав роли, создание роли из описания | [Подробнее](docs/role-guide.md) | +| Роли (Role) | 3 навыка `/role-*` | Анализ прав роли, создание из JSON DSL, валидация | [Подробнее](docs/role-guide.md) | | Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — | ## Требования @@ -66,7 +66,8 @@ ├── form-edit/ # Добавление элементов в форму ├── form-patterns/ # Справочник паттернов компоновки форм ├── role-info/ # Анализ прав роли -├── role-compile/ # Создание роли из описания +├── role-compile/ # Создание роли из JSON DSL +├── role-validate/ # Валидация роли └── img-grid/ # Сетка для анализа изображений docs/ ├── epf-guide.md # Гайд: внешние обработки diff --git a/docs/role-guide.md b/docs/role-guide.md index ff40e964..5fd9dfeb 100644 --- a/docs/role-guide.md +++ b/docs/role-guide.md @@ -1,13 +1,73 @@ # Роли (Role) -Навыки группы `/role-*` позволяют анализировать и создавать роли 1С — XML-файлы прав доступа (Rights.xml) и метаданных. +Навыки группы `/role-*` позволяют анализировать, создавать и проверять роли 1С — XML-файлы прав доступа (Rights.xml) и метаданных. ## Навыки | Навык | Параметры | Описание | |-------|-----------|----------| | `/role-info` | `` | Компактная сводка прав: объекты по типам, только разрешённые, RLS, шаблоны | -| `/role-compile` | ` ` | Создание роли: метаданные + Rights.xml по описанию прав | +| `/role-compile` | ` ` | Генерация роли из JSON DSL: метаданные + Rights.xml, UUID автоматически | +| `/role-validate` | ` [MetadataPath]` | Валидация структурной корректности: XML, namespace, права, RLS, шаблоны | + +## Рабочий цикл + +``` +Описание прав (текст) → JSON DSL → /role-compile → XML-исходники → /role-validate + → /role-info +``` + +1. Claude формирует JSON-определение роли (с пресетами или явными правами) +2. `/role-compile` генерирует `Roles/ИмяРоли.xml` + `Roles/ИмяРоли/Ext/Rights.xml` +3. `/role-validate` проверяет корректность сгенерированного XML +4. `/role-info` выводит компактную сводку для визуальной проверки + +## JSON DSL — компактный формат + +Роли описываются в JSON с двумя уровнями детализации: + +### Строковый shorthand (простые роли) + +```json +{ + "name": "ЧтениеНоменклатуры", + "synonym": "Чтение номенклатуры", + "objects": [ + "Catalog.Номенклатура: @view", + "Catalog.Контрагенты: @view", + "DataProcessor.Загрузка: @use" + ] +} +``` + +Формат строки: `Тип.Имя: @пресет` или `Тип.Имя: Право1, Право2`. + +### Объектная форма (RLS, переопределения) + +```json +{ + "name": "Document.Реализация", + "preset": "view", + "rights": { "Delete": false }, + "rls": { "Read": "#ДляОбъекта(\"\")" } +} +``` + +Форматы можно смешивать в одном массиве `objects`. + +### Пресеты + +| Пресет | Действие | +|--------|----------| +| `@view` | Просмотр: Read, View, InputByString (для справочников/документов); Read, View (для регистров) | +| `@edit` | Полное редактирование: CRUD + Interactive* + Posting (для документов) | +| `@use` | Использование: Use, View (для обработок/отчётов/сервисов) | + +`@` обязателен в строковом shorthand. В объектной форме — ключ `preset` без `@`. + +### Русские синонимы + +Скрипт принимает русские имена типов и прав: `Справочник` → Catalog, `Чтение` → Read, `Проведение` → Posting и т.д. Полный список — в [SKILL.md навыка](../.claude/skills/role-compile/SKILL.md). ## Сценарии использования @@ -22,15 +82,6 @@ Claude вызовет `/role-info`, получит компактную свод - где есть ограничения RLS - какие шаблоны ограничений используются -### Создание роли для регламентного задания - -``` -> Проанализируй модуль регламентного задания ОбновлениеКурсовВалют -> и создай роль с минимальными правами для его выполнения -``` - -Claude проанализирует код, определит используемые объекты метаданных, и вызовет `/role-compile` для создания роли с нужными правами (Read, Update, Posting и т.д.). - ### Создание роли по описанию ``` @@ -41,11 +92,46 @@ Claude проанализирует код, определит использу > - Регистр ЦеныНоменклатуры: чтение ``` -Рабочий цикл: -1. Claude генерирует `Roles/МенеджерПродаж.xml` (метаданные с UUID) -2. Claude генерирует `Roles/МенеджерПродаж/Ext/Rights.xml` (права) -3. Регистрирует роль в `Configuration.xml` (``) -4. Проверяет результат через `/role-info` +Claude сформирует JSON с пресетами: +```json +{ + "name": "МенеджерПродаж", + "synonym": "Менеджер продаж", + "objects": [ + "Document.РеализацияТоваровУслуг: @edit", + "Catalog.Контрагенты: @view", + "Catalog.Номенклатура: @view", + "InformationRegister.ЦеныНоменклатуры: @view" + ] +} +``` + +И вызовет `/role-compile` → `/role-validate` → `/role-info`. + +### Создание роли для регламентного задания + +``` +> Проанализируй модуль регламентного задания ОбновлениеКурсовВалют +> и создай роль с минимальными правами для его выполнения +``` + +Claude проанализирует код, определит используемые объекты, создаст JSON с точечными правами (без пресетов — только нужные права), и скомпилирует роль. + +### Создание роли с RLS + +``` +> Создай роль для чтения документов с ограничением по организации +``` + +Claude использует объектную форму JSON с шаблонами ограничений. + +### Проверка существующей роли + +``` +> Проверь корректность роли Roles/МояРоль/Ext/Rights.xml +``` + +Claude вызовет `/role-validate` и покажет результат: ошибки (невалидный XML, отсутствующие элементы) и предупреждения (неизвестные типы объектов, подозрительные имена прав с подсказками). ## Структура файлов роли