fix(role-compile,role-edit): шесть находок ревью

1. Резолв uuid вложенного объекта переписан со сквозной регулярки на
   спуск по дереву. Поиск по всему файлу брал первый узел с подходящим
   именем, а реквизит шапки и реквизит табличной части сплошь и рядом
   называются одинаково — ключ сортировки был чужим и вдобавок
   дублировался, из-за чего порты могли разложить узлы по-разному.
   Дочерние подсистемы лежат отдельными файлами и не резолвились вовсе:
   431 узел в ролях ACC. Теперь на реальной роли резолвится 90% узлов,
   остальное — стандартные реквизиты, у которых uuid в выгрузке нет.
   Для них предупреждение больше не печатается: это норма, а не потеря.

2. Фильтр умолчаний уносил право вместе с его ограничением RLS: условие
   пропадало молча. Право с ограничением отличается от умолчания самим
   ограничением и остаётся.

3. modify-property не обновлял кэш умолчаний, и следующие операции того
   же вызова фильтровали по старому флагу — запись попадала в файл ровно
   вопреки тому, о чём навык сам предупреждает.

4. Edit-RoleMetadata перечитывал Roles/Имя.xml на каждой операции, и в
   пакете первая правка молча терялась: set-synonym + set-comment
   сохранял только комментарий. В py-порте такого не было — расхождение
   портов закрыто.

5. Атрибуция сообщения в PS шла подстрокой по всему списку отброшенного,
   из-за чего причина приписывалась чужому объекту. Теперь префиксное
   сравнение, как в py.

Три новых кейса, семьи get_rights_object_uuid и is_standard_kind
заведены в check-inline-drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGkXwoXTuafcu1SXMsauFq
This commit is contained in:
Nick Shirokov
2026-09-13 21:20:18 +03:00
co-authored by Claude Opus 5
parent 5ec732e12b
commit 5b00410485
47 changed files with 2551 additions and 67 deletions
+48 -13
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-edit v1.6 — Edit existing 1C role rights in place
# role-edit v1.7 — Edit existing 1C role rights in place
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -1041,8 +1041,21 @@ def sort_rights_canonical(object_name, rights):
return sorted_rights
# У стандартных реквизитов и стандартных табличных частей uuid в выгрузке нет: они системные.
# Отсутствие uuid для них — норма, а не потерянный объект.
def is_standard_kind(object_name):
parts = object_name.split('.')
if len(parts) < 3:
return False
return parts[-2].startswith("Standard")
# uuid объекта прав: у верхнего уровня — из файла объекта, у вложенного — спуском по дереву.
# Искать регуляркой по всему файлу нельзя: реквизит шапки и реквизит табличной части часто
# называются одинаково, и поиск нашёл бы первый попавшийся. Дочерние подсистемы лежат
# отдельными файлами, поэтому для них спуск идёт по каталогам.
# У стандартных реквизитов uuid в выгрузке нет вовсе — для них возвращаем None молча.
def get_rights_object_uuid(object_name, config_root):
"""uuid объекта прав: у верхнего уровня — из файла объекта, у вложенного — из его узла."""
parts = object_name.split('.')
if parts[0] == 'Configuration':
cfg_path = os.path.join(config_root, 'Configuration.xml')
@@ -1054,19 +1067,36 @@ def get_rights_object_uuid(object_name, config_root):
directory = TYPE_DIRS.get(parts[0])
if not directory or len(parts) < 2:
return None
# Подсистемы вложены каталогами: Subsystems/Родитель/Subsystems/Ребёнок.xml
owner_path = os.path.join(config_root, directory, parts[1] + '.xml')
i = 2
while len(parts) > i + 1 and parts[i] == 'Subsystem':
owner_path = os.path.join(os.path.splitext(owner_path)[0], 'Subsystems', parts[i + 1] + '.xml')
i += 2
if not os.path.isfile(owner_path):
return None
with open(owner_path, 'r', encoding='utf-8-sig') as f:
text = f.read()
if len(parts) == 2:
m = re.search(r'<%s uuid="([0-9a-fA-F-]+)"' % re.escape(parts[0]), text)
return m.group(1) if m else None
# Вложенный: вид — предпоследний сегмент, имя — последний.
rx = r'<%s uuid="([0-9a-fA-F-]+)"[^>]*>\s*<Properties>\s*<Name>%s</Name>' % (
re.escape(parts[-2]), re.escape(parts[-1]))
m = re.search(rx, text)
return m.group(1) if m else None
try:
tree = etree.parse(owner_path)
except Exception:
return None
md = '{http://v8.1c.ru/8.3/MDClasses}'
node = tree.getroot()[0] if len(tree.getroot()) else None
if node is None:
return None
# Оставшиеся пары «вид, имя» ищем строго внутри текущего узла.
while i + 1 < len(parts):
kind, name = parts[i], parts[i + 1]
child = None
for candidate in node.findall(f'{md}ChildObjects/{md}{kind}'):
props = candidate.find(f'{md}Properties/{md}Name')
if props is not None and (props.text or '') == name:
child = candidate
break
if child is None:
return None
node = child
i += 2
return node.get('uuid')
def sort_objects_by_uuid(objects, config_root):
@@ -1731,7 +1761,7 @@ class Editor:
if other and other > uuid_value:
ref = node
break
else:
elif not is_standard_kind(obj_name):
self.note(f"[WARN] {obj_name}: объект не найден в выгрузке, uuid неизвестен — "
f"узел записан перед шаблонами (платформа переставит его при первой выгрузке)")
if ref is None:
@@ -2095,6 +2125,11 @@ class Editor:
self.note(f" {spec['Name']}: уже {spec['Value']}, изменений нет")
return
node.text = spec["Value"]
# Умолчания решают, какие записи вообще пишутся, — следующие операции должны видеть новое значение.
if spec["Name"] == "setForNewObjects":
self.role_sfno = spec["Value"]
if spec["Name"] == "setForAttributesByDefault":
self.role_sfab = spec["Value"]
self.modify_count += 1
self.rights_dirty = True
self.note(f" {spec['Name']} = {spec['Value']}")