Инструменты пользователя

Инструменты сайта


sharixopen:knowledge:scenarios:access_request_declined

Это старая версия документа!


ACCESS_REQUEST в DECLINED: контрольный срок и безопасная очистка

Назначение: Сценарий процесса. Продукт: ShariX Open.

Для кого: Разработчик · Администратор · Поддержка · Рецензент и приёмщик · Нейросеть.

Темы: Обработчики → ACCESS_REQUEST · Доступ → Полномочия.

Область: ShariX Open. Обработчик: open_access_request_declined, тип ACCESS_REQUEST (3), рабочий статус DECLINED (359). 31.10.2026 опубликованные исходники повторно получены из Git. Обе Python-копии заново сопоставлены с результатом генерации Blockly; проверены 11 исходников генераторов. Сам handler не исполнялся. Выбранную версию, конфигурацию и внешние подключения сервера проверяют отдельно.

Назначение и границы

Обработчик сопровождает уже отклонённую заявку:

  • не принимает новое решение об отказе;
  • не отзывает active permission;
  • не удаляет строку Permissions физически;
  • после контрольного срока может пометить неактивное право status=2 (deleted);
  • отдельно переводит Ticket в CLOSED.

ACCREQ_AUTOACCEPT здесь не используется.

Для расчёта срока важно:

  • начало отсчёта — Ticket.updated_at;
  • дата создания и отдельная дата решения не используются вместо него;
  • новая сохранённая ревизия Ticket может сдвинуть срок;
  • на каждом проходе заново проверяются статус Ticket и permission.

Наступление таймера само по себе не гарантирует очистку.

Схемы имеют isExecutable=false и не запускают код. Runtime исполняет выбранный проверенный Python из Blockly. End event — завершение показанной операции, а не физическое удаление права или пользователя.

Один проход и контрольный срок (BPMN)

Схема BPMN

Загрузка схемы…

Исходник схемы
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:ns2="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:ns3="http://www.omg.org/spec/DD/20100524/DC" xmlns:ns4="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_access_declined_logic" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="declined_Process_declined" name="open_access_request_declined: проверка active, контрольный срок и очистка" isExecutable="false">
    <bpmn:documentation>{"notes": ["BPMN isExecutable=false; генератор не выполняет handler.", "grace_deadline=Ticket.updated_at + ACCESS_REQUEST_DECLINED_CLEANUP_GRACE_DAYS.", "deferred_until=min(grace_deadline, now+DEFERRED_TICKET_RECHECK_SECONDS) при ожидании grace.", "Soft delete означает status=2; active permission не удаляется этим workflow."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_access_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "3725a0a556c1cec48f66333b62b553a77095334dac1e77d1b06b95a0414dd0bc"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_access_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "8911b2a9d65d51a258b434e1e9fa4d4431a965aa3f195f62ac0d3c781d15a95e"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/data_parsers.py", "repository": "sharix-open-handlers-backend", "sha256": "d7f26f172f68e4b390f675de88fbfb3c37a58fb29d4bf3d6687b31bd7953f9e4"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "classes/AccessRequestTicket.py", "repository": "sharix-open-handlers-backend", "sha256": "3f3a498ae76b3d878b19a8f32170fc2a71c6c6c182bf34a6ecc50edf7b0f5eff"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}]}</bpmn:documentation>
    <bpmn:laneSet id="declined_Lanes_declined">
      <bpmn:lane id="declined_Lane_worker" name="Worker: один проход Python">
        <bpmn:flowNodeRef>declined_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_status</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_lookup</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_active</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_notify</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_active_wait</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_stale</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_stale_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_age</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_delete</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_close</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_confirmed</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_remove</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_not_found</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_grace</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_lookup_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_delete_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_close_error</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_retry</bpmn:flowNodeRef>
      </bpmn:lane>
      <bpmn:lane id="declined_Lane_queue" name="Планировщик: ожидание без занятого worker">
        <bpmn:flowNodeRef>declined_wait</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_timer</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_revision</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>declined_ready</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="declined_start" name="Ticket готов к проходу">
      <bpmn:outgoing>declined_Flow_0</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:exclusiveGateway id="declined_status" name="Статус DECLINED?">
      <bpmn:incoming>declined_Flow_0</bpmn:incoming>
      <bpmn:incoming>declined_Flow_25</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_1</bpmn:outgoing>
      <bpmn:outgoing>declined_Flow_2</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="declined_lookup" name="Scoped API: прочитать permission">
      <bpmn:incoming>declined_Flow_1</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_4</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="declined_active" name="Permission active?">
      <bpmn:incoming>declined_Flow_4</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_5</bpmn:outgoing>
      <bpmn:outgoing>declined_Flow_7</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:sendTask id="declined_notify" name="ALERT администраторам: active при DECLINED">
      <bpmn:incoming>declined_Flow_5</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_6</bpmn:outgoing>
    </bpmn:sendTask>
    <bpmn:serviceTask id="declined_active_wait" name="related_permission_active; defer">
      <bpmn:documentation>ACCESS_REQUEST_INCONSISTENCY_RECHECK_SECONDS</bpmn:documentation>
      <bpmn:incoming>declined_Flow_6</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_15</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="declined_stale" name="Убрать Ticket и комнату">
      <bpmn:incoming>declined_Flow_2</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_3</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="declined_stale_end" name="Этот handler больше не владелец">
      <bpmn:incoming>declined_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:exclusiveGateway id="declined_age" name="Срок от updated_at уже наступил?">
      <bpmn:incoming>declined_Flow_7</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_8</bpmn:outgoing>
      <bpmn:outgoing>declined_Flow_9</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="declined_delete" name="Повторно прочитать и soft-delete неактивную permission">
      <bpmn:incoming>declined_Flow_8</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_10</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="declined_close" name="Scoped Ticket API: CLOSED">
      <bpmn:incoming>declined_Flow_10</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_11</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="declined_confirmed" name="Переход подтверждён?">
      <bpmn:incoming>declined_Flow_11</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_12</bpmn:outgoing>
      <bpmn:outgoing>declined_Flow_14</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="declined_remove" name="Убрать Ticket и комнату">
      <bpmn:incoming>declined_Flow_12</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_13</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:endEvent id="declined_end" name="Этот проход завершён">
      <bpmn:incoming>declined_Flow_13</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:endEvent id="declined_not_found" name="404: очистка запрошена">
      <bpmn:incoming>declined_Flow_14</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:serviceTask id="declined_grace" name="declined_permission_grace; назначить короткий defer">
      <bpmn:documentation>До min(grace_deadline, now+DEFERRED_TICKET_RECHECK_SECONDS); не один сон до конца всего grace.</bpmn:documentation>
      <bpmn:incoming>declined_Flow_9</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_16</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:boundaryEvent id="declined_lookup_error" name="Ошибка read API" attachedToRef="declined_lookup" cancelActivity="true">
      <bpmn:outgoing>declined_Flow_17</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:boundaryEvent id="declined_delete_error" name="Ошибка / active race" attachedToRef="declined_delete" cancelActivity="true">
      <bpmn:outgoing>declined_Flow_18</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:boundaryEvent id="declined_close_error" name="Ошибка перехода" attachedToRef="declined_close" cancelActivity="true">
      <bpmn:outgoing>declined_Flow_19</bpmn:outgoing>
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:serviceTask id="declined_retry" name="processing_error + общий defer">
      <bpmn:incoming>declined_Flow_17</bpmn:incoming>
      <bpmn:incoming>declined_Flow_18</bpmn:incoming>
      <bpmn:incoming>declined_Flow_19</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_20</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:eventBasedGateway id="declined_wait" name="Что наступило раньше?">
      <bpmn:incoming>declined_Flow_15</bpmn:incoming>
      <bpmn:incoming>declined_Flow_16</bpmn:incoming>
      <bpmn:incoming>declined_Flow_20</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_21</bpmn:outgoing>
      <bpmn:outgoing>declined_Flow_22</bpmn:outgoing>
    </bpmn:eventBasedGateway>
    <bpmn:intermediateCatchEvent id="declined_timer" name="Наступил локальный срок defer">
      <bpmn:incoming>declined_Flow_21</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_23</bpmn:outgoing>
      <bpmn:timerEventDefinition>
        <bpmn:timeDate xsi:type="bpmn:tFormalExpression">deferred_until</bpmn:timeDate>
      </bpmn:timerEventDefinition>
    </bpmn:intermediateCatchEvent>
    <bpmn:intermediateCatchEvent id="declined_revision" name="Новая authoritative revision Ticket">
      <bpmn:incoming>declined_Flow_22</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_24</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:intermediateCatchEvent>
    <bpmn:serviceTask id="declined_ready" name="Снять defer, вернуть в ready FIFO">
      <bpmn:incoming>declined_Flow_23</bpmn:incoming>
      <bpmn:incoming>declined_Flow_24</bpmn:incoming>
      <bpmn:outgoing>declined_Flow_25</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sequenceFlow id="declined_Flow_0" sourceRef="declined_start" targetRef="declined_status" name="" />
    <bpmn:sequenceFlow id="declined_Flow_1" sourceRef="declined_status" targetRef="declined_lookup" name="Да" />
    <bpmn:sequenceFlow id="declined_Flow_2" sourceRef="declined_status" targetRef="declined_stale" name="Нет" />
    <bpmn:sequenceFlow id="declined_Flow_3" sourceRef="declined_stale" targetRef="declined_stale_end" name="" />
    <bpmn:sequenceFlow id="declined_Flow_4" sourceRef="declined_lookup" targetRef="declined_active" name="" />
    <bpmn:sequenceFlow id="declined_Flow_5" sourceRef="declined_active" targetRef="declined_notify" name="Да" />
    <bpmn:sequenceFlow id="declined_Flow_6" sourceRef="declined_notify" targetRef="declined_active_wait" name="" />
    <bpmn:sequenceFlow id="declined_Flow_7" sourceRef="declined_active" targetRef="declined_age" name="Нет / не найдена" />
    <bpmn:sequenceFlow id="declined_Flow_8" sourceRef="declined_age" targetRef="declined_delete" name="Да" />
    <bpmn:sequenceFlow id="declined_Flow_9" sourceRef="declined_age" targetRef="declined_grace" name="Нет" />
    <bpmn:sequenceFlow id="declined_Flow_10" sourceRef="declined_delete" targetRef="declined_close" name="" />
    <bpmn:sequenceFlow id="declined_Flow_11" sourceRef="declined_close" targetRef="declined_confirmed" name="" />
    <bpmn:sequenceFlow id="declined_Flow_12" sourceRef="declined_confirmed" targetRef="declined_remove" name="Да" />
    <bpmn:sequenceFlow id="declined_Flow_13" sourceRef="declined_remove" targetRef="declined_end" name="" />
    <bpmn:sequenceFlow id="declined_Flow_14" sourceRef="declined_confirmed" targetRef="declined_not_found" name="Нет (404)" />
    <bpmn:sequenceFlow id="declined_Flow_15" sourceRef="declined_active_wait" targetRef="declined_wait" name="" />
    <bpmn:sequenceFlow id="declined_Flow_16" sourceRef="declined_grace" targetRef="declined_wait" name="" />
    <bpmn:sequenceFlow id="declined_Flow_17" sourceRef="declined_lookup_error" targetRef="declined_retry" name="" />
    <bpmn:sequenceFlow id="declined_Flow_18" sourceRef="declined_delete_error" targetRef="declined_retry" name="" />
    <bpmn:sequenceFlow id="declined_Flow_19" sourceRef="declined_close_error" targetRef="declined_retry" name="" />
    <bpmn:sequenceFlow id="declined_Flow_20" sourceRef="declined_retry" targetRef="declined_wait" name="" />
    <bpmn:sequenceFlow id="declined_Flow_21" sourceRef="declined_wait" targetRef="declined_timer" name="" />
    <bpmn:sequenceFlow id="declined_Flow_22" sourceRef="declined_wait" targetRef="declined_revision" name="" />
    <bpmn:sequenceFlow id="declined_Flow_23" sourceRef="declined_timer" targetRef="declined_ready" name="" />
    <bpmn:sequenceFlow id="declined_Flow_24" sourceRef="declined_revision" targetRef="declined_ready" name="" />
    <bpmn:sequenceFlow id="declined_Flow_25" sourceRef="declined_ready" targetRef="declined_status" name="Следующий проход" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_access_declined_logic">
    <bpmn:participant id="declined_Pool_declined" name="open_access_request_declined: проверка active, контрольный срок и очистка" processRef="declined_Process_declined" />
    <bpmn:participant id="admins_Participant" name="MUC-комната заявки: администраторы разбирают несогласованность по штатным правилам" />
    <bpmn:messageFlow id="Message_access_declined_logic_0" sourceRef="declined_notify" targetRef="admins_Participant" name="ALERT: это уведомление, а не автоматический отзыв права" />
  </bpmn:collaboration>
  <ns2:BPMNDiagram id="Diagram_access_declined_logic">
    <ns2:BPMNPlane id="Plane_access_declined_logic" bpmnElement="Collaboration_access_declined_logic">
      <ns2:BPMNShape id="declined_DI_Pool_declined" bpmnElement="declined_Pool_declined" isHorizontal="true">
        <ns3:Bounds x="0.0" y="0.0" width="2390.0" height="1000.0" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_Lane_worker" bpmnElement="declined_Lane_worker" isHorizontal="true">
        <ns3:Bounds x="30.0" y="0.0" width="2360.0" height="605.0" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_Lane_queue" bpmnElement="declined_Lane_queue" isHorizontal="true">
        <ns3:Bounds x="30.0" y="605.0" width="2360.0" height="395.0" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_start" bpmnElement="declined_start">
        <ns3:Bounds x="132.0" y="92.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_status" bpmnElement="declined_status">
        <ns3:Bounds x="325.0" y="85.0" width="50" height="50" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_lookup" bpmnElement="declined_lookup">
        <ns3:Bounds x="515.0" y="75.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_active" bpmnElement="declined_active">
        <ns3:Bounds x="815.0" y="85.0" width="50" height="50" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_notify" bpmnElement="declined_notify">
        <ns3:Bounds x="1025.0" y="75.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_active_wait" bpmnElement="declined_active_wait">
        <ns3:Bounds x="1285.0" y="75.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_stale" bpmnElement="declined_stale">
        <ns3:Bounds x="275.0" y="275.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_stale_end" bpmnElement="declined_stale_end">
        <ns3:Bounds x="572.0" y="292.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_age" bpmnElement="declined_age">
        <ns3:Bounds x="815.0" y="285.0" width="50" height="50" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_delete" bpmnElement="declined_delete">
        <ns3:Bounds x="1025.0" y="275.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_close" bpmnElement="declined_close">
        <ns3:Bounds x="1285.0" y="275.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_confirmed" bpmnElement="declined_confirmed">
        <ns3:Bounds x="1595.0" y="285.0" width="50" height="50" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_remove" bpmnElement="declined_remove">
        <ns3:Bounds x="1805.0" y="275.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_end" bpmnElement="declined_end">
        <ns3:Bounds x="2092.0" y="292.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_not_found" bpmnElement="declined_not_found">
        <ns3:Bounds x="1602.0" y="472.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_grace" bpmnElement="declined_grace">
        <ns3:Bounds x="765.0" y="475.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_lookup_error" bpmnElement="declined_lookup_error">
        <ns3:Bounds x="622.0" y="127.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_delete_error" bpmnElement="declined_delete_error">
        <ns3:Bounds x="1132.0" y="327.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_close_error" bpmnElement="declined_close_error">
        <ns3:Bounds x="1392.0" y="327.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_retry" bpmnElement="declined_retry">
        <ns3:Bounds x="1805.0" y="475.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_wait" bpmnElement="declined_wait">
        <ns3:Bounds x="1075.0" y="745.0" width="50" height="50" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_timer" bpmnElement="declined_timer">
        <ns3:Bounds x="1342.0" y="682.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_revision" bpmnElement="declined_revision">
        <ns3:Bounds x="1342.0" y="882.0" width="36" height="36" />
      </ns2:BPMNShape>
      <ns2:BPMNShape id="declined_DI_ready" bpmnElement="declined_ready">
        <ns3:Bounds x="1545.0" y="735.0" width="150" height="70" />
      </ns2:BPMNShape>
      <ns2:BPMNEdge id="declined_DI_Flow_0" bpmnElement="declined_Flow_0">
        <ns4:waypoint x="168.0" y="110.0" />
        <ns4:waypoint x="325.0" y="110.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_1" bpmnElement="declined_Flow_1">
        <ns4:waypoint x="375.0" y="110.0" />
        <ns4:waypoint x="515.0" y="110.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_2" bpmnElement="declined_Flow_2">
        <ns4:waypoint x="350.0" y="135.0" />
        <ns4:waypoint x="350.0" y="275.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_3" bpmnElement="declined_Flow_3">
        <ns4:waypoint x="425.0" y="310.0" />
        <ns4:waypoint x="572.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_4" bpmnElement="declined_Flow_4">
        <ns4:waypoint x="665.0" y="110.0" />
        <ns4:waypoint x="815.0" y="110.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_5" bpmnElement="declined_Flow_5">
        <ns4:waypoint x="865.0" y="110.0" />
        <ns4:waypoint x="1025.0" y="110.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_6" bpmnElement="declined_Flow_6">
        <ns4:waypoint x="1175.0" y="110.0" />
        <ns4:waypoint x="1285.0" y="110.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_7" bpmnElement="declined_Flow_7">
        <ns4:waypoint x="840.0" y="135.0" />
        <ns4:waypoint x="840.0" y="285.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_8" bpmnElement="declined_Flow_8">
        <ns4:waypoint x="865.0" y="310.0" />
        <ns4:waypoint x="1025.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_9" bpmnElement="declined_Flow_9">
        <ns4:waypoint x="840.0" y="335.0" />
        <ns4:waypoint x="840.0" y="475.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_10" bpmnElement="declined_Flow_10">
        <ns4:waypoint x="1175.0" y="310.0" />
        <ns4:waypoint x="1285.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_11" bpmnElement="declined_Flow_11">
        <ns4:waypoint x="1435.0" y="310.0" />
        <ns4:waypoint x="1595.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_12" bpmnElement="declined_Flow_12">
        <ns4:waypoint x="1645.0" y="310.0" />
        <ns4:waypoint x="1805.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_13" bpmnElement="declined_Flow_13">
        <ns4:waypoint x="1955.0" y="310.0" />
        <ns4:waypoint x="2092.0" y="310.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_14" bpmnElement="declined_Flow_14">
        <ns4:waypoint x="1620.0" y="335.0" />
        <ns4:waypoint x="1620.0" y="472.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_15" bpmnElement="declined_Flow_15">
        <ns4:waypoint x="1435.0" y="110.0" />
        <ns4:waypoint x="1490.0" y="110.0" />
        <ns4:waypoint x="1490.0" y="620.0" />
        <ns4:waypoint x="1100.0" y="620.0" />
        <ns4:waypoint x="1100.0" y="745.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_16" bpmnElement="declined_Flow_16">
        <ns4:waypoint x="915.0" y="510.0" />
        <ns4:waypoint x="995.0" y="510.0" />
        <ns4:waypoint x="995.0" y="770.0" />
        <ns4:waypoint x="1075.0" y="770.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_17" bpmnElement="declined_Flow_17">
        <ns4:waypoint x="658.0" y="145.0" />
        <ns4:waypoint x="700.0" y="145.0" />
        <ns4:waypoint x="700.0" y="590.0" />
        <ns4:waypoint x="1880.0" y="590.0" />
        <ns4:waypoint x="1880.0" y="475.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_18" bpmnElement="declined_Flow_18">
        <ns4:waypoint x="1168.0" y="345.0" />
        <ns4:waypoint x="1210.0" y="345.0" />
        <ns4:waypoint x="1210.0" y="580.0" />
        <ns4:waypoint x="1880.0" y="580.0" />
        <ns4:waypoint x="1880.0" y="475.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_19" bpmnElement="declined_Flow_19">
        <ns4:waypoint x="1428.0" y="345.0" />
        <ns4:waypoint x="1470.0" y="345.0" />
        <ns4:waypoint x="1880.0" y="345.0" />
        <ns4:waypoint x="1880.0" y="475.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_20" bpmnElement="declined_Flow_20">
        <ns4:waypoint x="1955.0" y="510.0" />
        <ns4:waypoint x="2010.0" y="510.0" />
        <ns4:waypoint x="2010.0" y="640.0" />
        <ns4:waypoint x="1100.0" y="640.0" />
        <ns4:waypoint x="1100.0" y="745.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_21" bpmnElement="declined_Flow_21">
        <ns4:waypoint x="1125.0" y="770.0" />
        <ns4:waypoint x="1233.5" y="770.0" />
        <ns4:waypoint x="1233.5" y="700.0" />
        <ns4:waypoint x="1342.0" y="700.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_22" bpmnElement="declined_Flow_22">
        <ns4:waypoint x="1125.0" y="770.0" />
        <ns4:waypoint x="1233.5" y="770.0" />
        <ns4:waypoint x="1233.5" y="900.0" />
        <ns4:waypoint x="1342.0" y="900.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_23" bpmnElement="declined_Flow_23">
        <ns4:waypoint x="1378.0" y="700.0" />
        <ns4:waypoint x="1461.5" y="700.0" />
        <ns4:waypoint x="1461.5" y="770.0" />
        <ns4:waypoint x="1545.0" y="770.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_24" bpmnElement="declined_Flow_24">
        <ns4:waypoint x="1378.0" y="900.0" />
        <ns4:waypoint x="1461.5" y="900.0" />
        <ns4:waypoint x="1461.5" y="770.0" />
        <ns4:waypoint x="1545.0" y="770.0" />
      </ns2:BPMNEdge>
      <ns2:BPMNEdge id="declined_DI_Flow_25" bpmnElement="declined_Flow_25">
        <ns4:waypoint x="1695.0" y="770.0" />
        <ns4:waypoint x="2260.0" y="770.0" />
        <ns4:waypoint x="2260.0" y="25.0" />
        <ns4:waypoint x="350.0" y="25.0" />
        <ns4:waypoint x="350.0" y="85.0" />
        <ns2:BPMNLabel>
          <ns3:Bounds x="1950.0" y="5.0" width="180" height="26" />
        </ns2:BPMNLabel>
      </ns2:BPMNEdge>
      <ns2:BPMNShape id="DI_admins_Participant" bpmnElement="admins_Participant" isHorizontal="true">
        <ns3:Bounds x="0" y="1120" width="2300" height="100" />
      </ns2:BPMNShape>
      <ns2:BPMNEdge id="DI_Message_access_declined_logic_0" bpmnElement="Message_access_declined_logic_0">
        <ns4:waypoint x="1100" y="145" />
        <ns4:waypoint x="1100" y="210" />
        <ns4:waypoint x="2350" y="210" />
        <ns4:waypoint x="2350" y="1050" />
        <ns4:waypoint x="1150" y="1050" />
        <ns4:waypoint x="1150" y="1120" />
        <ns2:BPMNLabel>
          <ns3:Bounds x="1520" y="1020" width="260" height="42" />
        </ns2:BPMNLabel>
      </ns2:BPMNEdge>
    </ns2:BPMNPlane>
  </ns2:BPMNDiagram>
</bpmn:definitions>
  • Не свой статус — запросить удаление локального Ticket и комнаты.
  • Связанная permission active — отправить ALERT администраторам, записать related_permission_active и ждать по ACCESS_REQUEST_INCONSISTENCY_RECHECK_SECONDS. До проверки grace и очистки эта ветвь не доходит.
  • Permission не active либо read не нашёл её — сравнить возраст Ticket.updated_at с ACCESS_REQUEST_DECLINED_CLEANUP_GRACE_DAYS.
  • Срок ещё не наступил — записать локальную причину declined_permission_grace и назначить короткий повтор до ближайшего контрольного момента.
  • Срок наступил — повторно прочитать permission, выполнить разрешённый soft delete, затем отдельно запросить CLOSED и после подтверждения убрать Ticket/комнату.

Read permission привязан к текущему типу/статусу Ticket и handler identity. 404 возвращается как отсутствие объекта, но может означать также устаревший scope. Несколько связанных permission дают 409 и общий retry, а не произвольный выбор записи. Поэтому диагностика «не найдена» включает актуальный статус и связь Ticket, а не только поиск строки Permissions.

Как рассчитывается ожидание

Условие готовности: возраст updated_at не меньше количества дней из ACCESS_REQUEST_DECLINED_CLEANUP_GRACE_DAYS. В коде допускается 0 дней, то есть немедленная проверка очистки при соблюдении остальных условий.

При ожидании конечная граница — updated_at + grace_days. Следующий локальный срок — минимум этой границы и now + DEFERRED_TICKET_RECHECK_SECONDS. Это короткие повторные проверки, а не занятый worker и не один длинный сон до конца всего grace. В журнал передаётся пояснение об ожидании контрольного срока; сохранённая локальная причина остаётся declined_permission_grace.

Если updated_at отсутствует или не разбирается, проверка возраста возвращает false, а helper расчёта defer не устанавливает срок. Это не подтверждённое ожидание по корректному grace: необходимо исправить/сверить данные Ticket. Страница не предполагает конкретного значения интервалов на сервере.

Backend повторяет защиту от удаления active (BPMN)

Схема BPMN

Загрузка схемы…

Исходник схемы
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_access_declined_cleanup" targetNamespace="urn:sharix:knowledge:handlers">
  <bpmn:process id="permission_Process_permission" name="Backend permission API: action=soft_delete" isExecutable="false">
    <bpmn:documentation>{"notes": ["BPMN isExecutable=false; генератор не выполняет handler.", "grace_deadline=Ticket.updated_at + ACCESS_REQUEST_DECLINED_CLEANUP_GRACE_DAYS.", "deferred_until=min(grace_deadline, now+DEFERRED_TICKET_RECHECK_SECONDS) при ожидании grace.", "Soft delete означает status=2; active permission не удаляется этим workflow.", "Показаны отдельные API-операции; общего commit soft-delete и CLOSED нет."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_access_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "3725a0a556c1cec48f66333b62b553a77095334dac1e77d1b06b95a0414dd0bc"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_access_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "8911b2a9d65d51a258b434e1e9fa4d4431a965aa3f195f62ac0d3c781d15a95e"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/data_parsers.py", "repository": "sharix-open-handlers-backend", "sha256": "d7f26f172f68e4b390f675de88fbfb3c37a58fb29d4bf3d6687b31bd7953f9e4"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "classes/AccessRequestTicket.py", "repository": "sharix-open-handlers-backend", "sha256": "3f3a498ae76b3d878b19a8f32170fc2a71c6c6c182bf34a6ecc50edf7b0f5eff"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}]}</bpmn:documentation>
    <bpmn:laneSet id="permission_Lanes_permission">
      <bpmn:lane id="permission_Lane_worker" name="Транзакция изменения связанного объекта">
        <bpmn:flowNodeRef>permission_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_lock</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_find</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_active</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_save</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_ok</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_end</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_reject</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>permission_missing</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="permission_start" name="Запрос soft_delete">
      <bpmn:outgoing>permission_Flow_0</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="permission_lock" name="Identity/capability/scope; блокировки">
      <bpmn:incoming>permission_Flow_0</bpmn:incoming>
      <bpmn:outgoing>permission_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="permission_find" name="Разрешить единственную permission по Ticket">
      <bpmn:incoming>permission_Flow_1</bpmn:incoming>
      <bpmn:outgoing>permission_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:exclusiveGateway id="permission_active" name="Сохранённый status=0?">
      <bpmn:incoming>permission_Flow_2</bpmn:incoming>
      <bpmn:outgoing>permission_Flow_3</bpmn:outgoing>
      <bpmn:outgoing>permission_Flow_4</bpmn:outgoing>
    </bpmn:exclusiveGateway>
    <bpmn:serviceTask id="permission_save" name="Сохранить status=2 и Event">
      <bpmn:incoming>permission_Flow_3</bpmn:incoming>
      <bpmn:outgoing>permission_Flow_5</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sendTask id="permission_ok" name="Успешный ответ">
      <bpmn:incoming>permission_Flow_5</bpmn:incoming>
      <bpmn:outgoing>permission_Flow_6</bpmn:outgoing>
    </bpmn:sendTask>
    <bpmn:endEvent id="permission_end" name="Soft delete зафиксирован">
      <bpmn:incoming>permission_Flow_6</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:endEvent id="permission_reject" name="Отказ: active не удаляется">
      <bpmn:incoming>permission_Flow_4</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:boundaryEvent id="permission_missing" name="404 / 409" attachedToRef="permission_find" cancelActivity="true">
      <bpmn:errorEventDefinition />
    </bpmn:boundaryEvent>
    <bpmn:sequenceFlow id="permission_Flow_0" sourceRef="permission_start" targetRef="permission_lock" name="" />
    <bpmn:sequenceFlow id="permission_Flow_1" sourceRef="permission_lock" targetRef="permission_find" name="" />
    <bpmn:sequenceFlow id="permission_Flow_2" sourceRef="permission_find" targetRef="permission_active" name="" />
    <bpmn:sequenceFlow id="permission_Flow_3" sourceRef="permission_active" targetRef="permission_save" name="Нет" />
    <bpmn:sequenceFlow id="permission_Flow_4" sourceRef="permission_active" targetRef="permission_reject" name="Да" />
    <bpmn:sequenceFlow id="permission_Flow_5" sourceRef="permission_save" targetRef="permission_ok" name="" />
    <bpmn:sequenceFlow id="permission_Flow_6" sourceRef="permission_ok" targetRef="permission_end" name="" />
  </bpmn:process>
  <bpmn:process id="ticket_Process_ticket" name="Backend Ticket API: отдельный запрос CLOSED после подтверждения очистки" isExecutable="false">
    <bpmn:documentation>{"notes": ["BPMN isExecutable=false; генератор не выполняет handler.", "grace_deadline=Ticket.updated_at + ACCESS_REQUEST_DECLINED_CLEANUP_GRACE_DAYS.", "deferred_until=min(grace_deadline, now+DEFERRED_TICKET_RECHECK_SECONDS) при ожидании grace.", "Soft delete означает status=2; active permission не удаляется этим workflow.", "Показаны отдельные API-операции; общего commit soft-delete и CLOSED нет."], "sources": [{"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "handlers/open_access_request_declined.py", "repository": "sharix-open-handlers-examples", "sha256": "3725a0a556c1cec48f66333b62b553a77095334dac1e77d1b06b95a0414dd0bc"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "generated_handler_blocks/open_access_request_declined.json", "repository": "sharix-open-handlers-examples", "sha256": "8911b2a9d65d51a258b434e1e9fa4d4431a965aa3f195f62ac0d3c781d15a95e"}, {"commit": "76e0c693b628ed4a1fa34515a3f8abdff26149bb", "path": "catalog.py", "repository": "sharix-open-handlers-examples", "sha256": "e3dcb72b7e620e2837adacd7af053f35b04cdff475789496d9c114e17d57dfde"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/blockly_runtime.py", "repository": "sharix-open-handlers-backend", "sha256": "3e222150cbff315077c6c9b160d46e59856cedf6d6591d213e958c63cb2f2428"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/handler_operations.py", "repository": "sharix-open-handlers-backend", "sha256": "28620658501c375f556e24437871620b84a17a88d0b62b2d1a3cc5deb2f14b1e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/data_parsers.py", "repository": "sharix-open-handlers-backend", "sha256": "d7f26f172f68e4b390f675de88fbfb3c37a58fb29d4bf3d6687b31bd7953f9e4"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/ejabberd_muc.py", "repository": "sharix-open-handlers-backend", "sha256": "35c2eba89fab36b5c444439a87beaa516b55133991136d5370418d92a18d7bcd"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "core/requesters.py", "repository": "sharix-open-handlers-backend", "sha256": "965a348cea477722bbaf4c8c6b06023eb238b71664a332f7ea8c90bfe72ea808"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "classes/AccessRequestTicket.py", "repository": "sharix-open-handlers-backend", "sha256": "3f3a498ae76b3d878b19a8f32170fc2a71c6c6c182bf34a6ecc50edf7b0f5eff"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_blockly_codegen.py", "repository": "sharix-open-handlers-backend", "sha256": "bce71eab622922f8dd0954222842349799b60e18a0ab6310cc3e5f305603661e"}, {"commit": "50baf376e224d915a87d63240f3f339b3dda9afa", "path": "ticket_codegen_contract.json", "repository": "sharix-open-handlers-backend", "sha256": "0bfd2b0ebb07bd380ed3c867386536ab88ef7dfa6e13f1cd8c91d32bb02c1ae9"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "lib/tickets.py", "repository": "sharix-open-tickets", "sha256": "0d260c17947f9a186d07af2749159780e3c7354a95b4bafe9d67493663c96883"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/views/handler_workflow.py", "repository": "sharix-open-tickets", "sha256": "f97c4b467a9a30749f5c4b86b39a833c430e57aaa1f030f70030d9b5c609d368"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "api/handler_auth.py", "repository": "sharix-open-tickets", "sha256": "ac76dc45aaa9742e5e1da0b98aae914bb2e287048d33e97b4a245a6997bc8425"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "utils.py", "repository": "sharix-open-tickets", "sha256": "a5680205c6522e1d9a74d9d267fe9ee96faf6f06436b00877fc0a93d80234bf5"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "views/ticket_detail.py", "repository": "sharix-open-tickets", "sha256": "05d344b0fba14cf895400694d4a18f486c800fef08dd1acb43ea46909b9e7e59"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "signals.py", "repository": "sharix-open-tickets", "sha256": "39aad8690a2e7d35cebfb052127a8130a951cbb4eea316c4f767a0a59fc5fa7e"}, {"commit": "fd64704d7d9f01a5ec5b7b89cbc5afe5102b2c14", "path": "services/handler_wakeup.py", "repository": "sharix-open-tickets", "sha256": "c9fdc62a18301fd7c075395c697d5b1c738c77713aa71fcbb5ab097e173d98de"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "apiviews/handler_workflow.py", "repository": "sharix-open-backend", "sha256": "0f699a4e16760cbbdd09d894093c685ba69073fedf0522852cb1fdc9811636e3"}, {"commit": "778a3280cee115ba0efe3fdff7b0e708b0c10093", "path": "lib/dicts.py", "repository": "sharix-open-backend", "sha256": "d92ddaa0964324e337303f8de110acf6ff21e15d7b40a6e523ce78ed48b7d8c5"}]}</bpmn:documentation>
    <bpmn:laneSet id="ticket_Lanes_ticket">
      <bpmn:lane id="ticket_Lane_worker" name="Отдельная транзакция">
        <bpmn:flowNodeRef>ticket_start</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_scope</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_change</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_wake</bpmn:flowNodeRef>
        <bpmn:flowNodeRef>ticket_done</bpmn:flowNodeRef>
      </bpmn:lane>
    </bpmn:laneSet>
    <bpmn:startEvent id="ticket_start" name="Handler запросил CLOSED">
      <bpmn:outgoing>ticket_Flow_0</bpmn:outgoing>
      <bpmn:messageEventDefinition />
    </bpmn:startEvent>
    <bpmn:serviceTask id="ticket_scope" name="Проверить capability и актуальный scope">
      <bpmn:incoming>ticket_Flow_0</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_1</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:serviceTask id="ticket_change" name="Проверить lifecycle; сохранить CLOSED и Event">
      <bpmn:incoming>ticket_Flow_1</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_2</bpmn:outgoing>
    </bpmn:serviceTask>
    <bpmn:sendTask id="ticket_wake" name="После commit: ticket.moved прежнему владельцу">
      <bpmn:incoming>ticket_Flow_2</bpmn:incoming>
      <bpmn:outgoing>ticket_Flow_3</bpmn:outgoing>
    </bpmn:sendTask>
    <bpmn:endEvent id="ticket_done" name="Ticket закрыт, очередь освобождается">
      <bpmn:incoming>ticket_Flow_3</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="ticket_Flow_0" sourceRef="ticket_start" targetRef="ticket_scope" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_1" sourceRef="ticket_scope" targetRef="ticket_change" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_2" sourceRef="ticket_change" targetRef="ticket_wake" name="" />
    <bpmn:sequenceFlow id="ticket_Flow_3" sourceRef="ticket_wake" targetRef="ticket_done" name="" />
  </bpmn:process>
  <bpmn:collaboration id="Collaboration_access_declined_cleanup">
    <bpmn:participant id="permission_Pool_permission" name="Backend permission API: action=soft_delete" processRef="permission_Process_permission" />
    <bpmn:participant id="ticket_Pool_ticket" name="Backend Ticket API: отдельный запрос CLOSED после подтверждения очистки" processRef="ticket_Process_ticket" />
    <bpmn:messageFlow id="Message_access_declined_cleanup_0" sourceRef="permission_ok" targetRef="ticket_start" name="Ответ разрешает handler перейти к отдельному запросу CLOSED" />
  </bpmn:collaboration>
  <bpmndi:BPMNDiagram id="Diagram_access_declined_cleanup">
    <bpmndi:BPMNPlane id="Plane_access_declined_cleanup" bpmnElement="Collaboration_access_declined_cleanup">
      <bpmndi:BPMNShape id="permission_DI_Pool_permission" bpmnElement="permission_Pool_permission" isHorizontal="true">
        <dc:Bounds x="0.0" y="0.0" width="1940" height="410" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_Lane_worker" bpmnElement="permission_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="0.0" width="1910" height="410" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_start" bpmnElement="permission_start">
        <dc:Bounds x="132.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_lock" bpmnElement="permission_lock">
        <dc:Bounds x="345.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_find" bpmnElement="permission_find">
        <dc:Bounds x="625.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_active" bpmnElement="permission_active">
        <dc:Bounds x="955.0" y="85.0" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_save" bpmnElement="permission_save">
        <dc:Bounds x="1185.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_ok" bpmnElement="permission_ok">
        <dc:Bounds x="1455.0" y="75.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_end" bpmnElement="permission_end">
        <dc:Bounds x="1762.0" y="92.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_reject" bpmnElement="permission_reject">
        <dc:Bounds x="962.0" y="292.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="permission_DI_missing" bpmnElement="permission_missing">
        <dc:Bounds x="732.0" y="127.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="permission_DI_Flow_0" bpmnElement="permission_Flow_0">
        <di:waypoint x="168.0" y="110.0" />
        <di:waypoint x="345.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_1" bpmnElement="permission_Flow_1">
        <di:waypoint x="495.0" y="110.0" />
        <di:waypoint x="625.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_2" bpmnElement="permission_Flow_2">
        <di:waypoint x="775.0" y="110.0" />
        <di:waypoint x="955.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_3" bpmnElement="permission_Flow_3">
        <di:waypoint x="1005.0" y="110.0" />
        <di:waypoint x="1185.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_4" bpmnElement="permission_Flow_4">
        <di:waypoint x="980.0" y="135.0" />
        <di:waypoint x="980.0" y="292.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_5" bpmnElement="permission_Flow_5">
        <di:waypoint x="1335.0" y="110.0" />
        <di:waypoint x="1455.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="permission_DI_Flow_6" bpmnElement="permission_Flow_6">
        <di:waypoint x="1605.0" y="110.0" />
        <di:waypoint x="1762.0" y="110.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="ticket_DI_Pool_ticket" bpmnElement="ticket_Pool_ticket" isHorizontal="true">
        <dc:Bounds x="0.0" y="590.0" width="1420" height="210" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_Lane_worker" bpmnElement="ticket_Lane_worker" isHorizontal="true">
        <dc:Bounds x="30.0" y="590.0" width="1390" height="210" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_start" bpmnElement="ticket_start">
        <dc:Bounds x="132.0" y="682.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_scope" bpmnElement="ticket_scope">
        <dc:Bounds x="345.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_change" bpmnElement="ticket_change">
        <dc:Bounds x="625.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_wake" bpmnElement="ticket_wake">
        <dc:Bounds x="905.0" y="665.0" width="150" height="70" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="ticket_DI_done" bpmnElement="ticket_done">
        <dc:Bounds x="1242.0" y="682.0" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_0" bpmnElement="ticket_Flow_0">
        <di:waypoint x="168.0" y="700.0" />
        <di:waypoint x="345.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_1" bpmnElement="ticket_Flow_1">
        <di:waypoint x="495.0" y="700.0" />
        <di:waypoint x="625.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_2" bpmnElement="ticket_Flow_2">
        <di:waypoint x="775.0" y="700.0" />
        <di:waypoint x="905.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="ticket_DI_Flow_3" bpmnElement="ticket_Flow_3">
        <di:waypoint x="1055.0" y="700.0" />
        <di:waypoint x="1242.0" y="700.0" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="DI_Message_access_declined_cleanup_0" bpmnElement="Message_access_declined_cleanup_0">
        <di:waypoint x="1530" y="145" />
        <di:waypoint x="1530" y="500" />
        <di:waypoint x="150" y="500" />
        <di:waypoint x="150" y="682" />
        <bpmndi:BPMNLabel>
          <dc:Bounds x="500" y="465" width="260" height="42" />
        </bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

Повторное чтение permission перед очисткой даёт разные пути:

  • Permission не найдена: soft_delete_related_permission возвращается без удаления; handler затем пробует CLOSED.
  • Permission стала active: возникает ошибка; эта ветвь автоматически Ticket не закрывает.
  • Неактивная permission найдена: выполняется scoped soft_delete, backend повторяет защиту.

Для существующей неактивной permission запрос идёт на dbsynce/api/handler/ticket/{ticket_id}/permission/ с действием soft_delete. Backend требует capability related.permission.status, текущий Ticket scope и единственную связь, блокирует Ticket/permission и повторно проверяет сохранённый статус. Если право стало active между чтениями и записью, операция отклоняется. Такая гонка не разрешает удалить active право по устаревшему снимку.

Успехи двух операций подтверждают отдельно:

  • Soft delete: сохранены status=2 и Event; это не SQL DELETE, не удаление пользователя и не отзыв иных прав.
  • Закрытие Ticket: следующий scoped запрос перевёл его в CLOSED.

Общего commit для этих двух запросов нет. Permission уже может быть deleted, а Ticket ещё DECLINED из-за ошибки второго запроса.

Техническая стрелка между двумя API-пулами показывает последовательность запросов handler после ответа первой операции, а не прямой серверный вызов одного API другим. Случай отсутствующей permission является no-op на стороне runtime и не требует успешного POST soft_delete перед попыткой закрытия; он описан отдельно выше.

Внешнее решение и уведомление

ALERT при active permission направляется в MUC-комнату Ticket по правилам PENDING. Администратор должен разобраться, согласованы ли решение Ticket и действующее право, используя штатные права и доменные операции. Handler не выбирает вместо него деактивацию и не считает текст ответа в комнате командой на удаление.

В этих сообщениях нет durable verification ack; повторные проходы могут повторять ALERT. Сообщение не доказывает прочтение и не выдаёт право менять Ticket. В текущем lifecycle из DECLINED допускаются ACCEPTED или CLOSED при соответствующих правах; изменение одного Ticket-статуса само по себе не создаёт, не активирует и не отзывает право.

После канонического перехода backend публикует события после commit. ticket.moved исключает снимок прежнего handler, в том числе через pending discard для занятого worker. Для ACCEPTED действует соответствующий handler; для CLOSED отдельного open_access_request_closed нет. Потерю XMPP-событий восстанавливает periodic reconciliation.

Ошибки и диагностика

Состояние Повтор / действие
Active permission при DECLINED ALERT, related_permission_active, ACCESS_REQUEST_INCONSISTENCY_RECHECK_SECONDS
Grace ещё не истёк declined_permission_grace, короткий defer до min контрольного срока и общего recheck
Ошибка read/soft-delete/уведомления/перехода вне локальной обработки processing_error, DEFERRED_TICKET_RECHECK_SECONDS
404 при переходе CLOSED Локальная очистка как при устаревшем scope
Потеря события / восстановление состава Scoped periodic список по INTERVAL_TIME_BD_REQ

Defer хранится в памяти и освобождает worker; новая authoritative revision может его снять, повтор того же снимка — нет. При рестарте состояние заново читается из backend. Ошибку API-списка нельзя считать пустой очередью.

При диагностике записать:

  • Ticket.status и актуальный updated_at;
  • рассчитанный deadline;
  • permission.status и связь с Ticket;
  • Event soft delete;
  • отдельный Event CLOSED.

После неопределённого ответа сначала сверяют фактическое состояние. Защиту active не снимают ради повторной очистки. Ожидание при active permission — предусмотренная остановка для разбора несогласованности.

Проверенные источники

Git SHA, SHA-256 и параметры моделей закреплены в metadata и documentation BPMN. Код handler при анализе не исполнялся. Изменение источников требует повторного сопоставления алгоритма и API/ACL, а не одной смены хешей.

Связанные материалы

sharixopen/knowledge/scenarios/access_request_declined.1793445618.txt.gz · Последнее изменение: — sx_knowledge_api_open

© 2022 ShariX