Методы графа
Методы функции графа: процессы и программы, запуски, переменные, вопросы, группы. Нужен тому, кто зовёт граф из окна, сценария или другой функции.
1 Методы функции графа
Документ — часть договора Обмен с ядром: методы, которыми мастерская и другие части изменяют процесс, включают программу и ведут прогон. Смысл графа, программы, прогона, переменных и вопросов — Программа: граф и запуски. Обёртка сообщения — Обмен с ядром, раздел 3, коды отказов — Коды ошибок.
Таблица 1 — Методы функции графа
| Метод | Тип | Параметры | Результат |
|---|---|---|---|
process.create |
команда | name |
{ process_id }; новый процесс уже содержит событие пульта run и переменную input типа text — его можно включить и запустить сразу, а поле пульта пишет переменную. Пульт и значения настроек процесса создают их хозяева, граф процесса записывается последним |
process.export |
команда | process_id, необязательно include_state |
{ path, process_id, modules, calls, links_left_out } — папка экспорта data/exports/<process_id>/, прежняя заменяется; с include_state: true в пакет ложится сохранённая память процесса — только с отметкой о сохранении и только та, которую не обслуживает поднятый сейчас модуль (module.state.export); calls — вызываемые процессы, вошедшие в пакет, modules — модули всех процессов пакета. Вызываемый процесс без графа — отказ экспорта (document_not_found). Документы других хозяев граф берёт у них: пульт — canvas.get, а его рисунки и виды — canvas.files.export, значения уровня процесса — settings.get с level: "process", версии модулей — module.lock.get |
process.delete |
команда | process_id |
{ process_id, deleted: true }: папка процесса удаляется целиком — граф, пульт, запуски и их файлы, запомненные значения, память узлов, окружение Python; событие process.changed с command: "process.delete". Включённую или идущую программу не удаляют — process_running; программу, которую вызывает другая, — process_in_use, и names называет вызывающие (Мастерская, раздел 4) |
process.inspect |
запрос | path экспорта |
{ path, name, nodes, documents, calls, modules, state } — что импорт принесёт, ничего не записывая; calls — вызываемые процессы { process_id, name, nodes }, модули — как у module.inspect, state — модули, чьи сохранённые состояния лежат в пакете |
process.import |
команда | path экспорта: папка ogas.export/3 |
{ process_id, calls, modules_added, modules_skipped, state }; calls — прежний id каждого вызываемого процесса → его новый id; state — модули, чьи сохранённые состояния из пакета стали состояниями нового процесса (module.state.import). Вызываемые процессы ставятся новыми процессами раньше вызывающего, вызовы переводятся на их новые id. Документы других хозяев принимают они сами: версии модулей — module.lock.import, пульт — canvas.save, значения уровня процесса — settings.set; чего в экспорте нет, делается как у нового процесса. Граф процесса пишется последним: отказ хозяина — отказ импорта, и процесса нет. Рисунки и виды пульта, если пакет их несёт, хозяин принимает после графа — canvas.files.import |
process.open |
запрос | process_id |
process.json |
process.save |
команда | process_id, document |
{ generation } |
process.validate |
запрос | документ или process_id |
{ ok, warnings } или ошибка graph_invalid; проверяет структуру, ключи подстановок, что рёбра идут через порты, которые у узлов есть (Программа: граф и запуски, раздел 4), и совместимость типов портов. warnings — то, что не отклоняет документ: { node_id, warning }, это no_hot_input (Программа: граф и запуски, раздел 6) |
process.space |
запрос | — | { parts: [{ key, title_key, bytes, clear_method? }] } — части места и сколько каждая занимает на диске; title_key — название части в словаре хозяина, clear_method — команда, которая убирает восстановимое в этой части (её зовут с { part: key }): программы с их файлами и памятью узлов и запуски с результатами; окружение Python процесса считает функция модулей |
process.list |
запрос | — | { processes }; процесс — { process_id, name, call }, где call — чем процесс является для узла, который его вызывает: { inputs, outputs, variables } или null, если вызвать его нельзя (Программа: граф и запуски, раздел 18) |
process.summary |
запрос | — | { processes } для библиотеки: у программы — { process_id, name, description, nodes, modules, bytes, environment_bytes, runs, changed, outline }: modules — модули её узлов, bytes — её папка без окружения Python, environment_bytes — окружение .venv, runs — сколько прошлых запусков хранит, changed — когда записан её документ, outline — очертание графа для превью: { nodes: [[x, y]], edges: [[из, в]] }, не больше 400 узлов, рёбра — номерами узлов. Ничего не пишет |
process.kinds |
запрос | — | { kinds, variables }: виды узлов и типы переменной процесса — { type, empty }, где empty — значение, с которого переменная этого типа начинается; окно берёт их отсюда и своего списка не держит |
process.node.template |
запрос | kind |
{ kind, template, about } — всё из описания вида в его папке functions/graph/src/kinds/<вид>/kind.json (правило 2.10). template — узел, с которого начинается новый: kind, parameters и, у узла-модуля, пустой module_id (порты и параметры узла-модуля окно берёт из module.list). about — что вид говорит о себе и что в документ узла не копируется: family — семейство вида, по которому холст красит шапку узла: start, work, flow, data или wait (Виды узлов, приложение А); inputs, outputs — названы, как в манифесте модуля, и у каждого порта есть type — у узла платформы всегда json (Программа: граф и запуски, раздел 4), так что окно читает тип порта вида и модуля одинаково и своего правила не держит (у узла-модуля — выход failed, который граф добавляет к портам манифеста, Программа: граф и запуски, 12.4), fields — что человек задаёт узлу: { path, how, choices }, где how — как читать набранное: text, number, json, value, choice (одно из choices), variable (переменная программы), process (один из процессов, которые можно вызвать) и values (по значению на каждую переменную вызываемого процесса, под путём поля); calls — у вида, узел которого вызывает процесс: имя параметра, которым узел его называет (process_id), — по этому признаку окно берёт порты узла из call вызываемого процесса, node_fields — что человек задаёт самому узлу помимо параметров (Форматы файлов, 5.1): { name, how, choices, module, words }. Первыми идут поля, которые есть у узла любого вида, — название узла name и его описание description (how: long — текст в несколько строк); их слова лежат в словаре самой функции графа, и words называет, с чего они начинаются (graph.node.field.name, graph.node.field.description). За ними — поля, которые добавляет вид; module называет, что должен говорить манифест модуля узла, чтобы поле имело смысл, — у overlap это { "process": "service" }. cold_source: true — узел вида отдаёт значение, и связь от него не ожидают: холст рисует её пунктиром, variable_use — у вида, узел которого называет переменную: что узел с ней делает, reads, writes или both (вид, который этого не сказал, читает); какой параметр называет переменную, говорит поле fields с how: "variable"; по этому признаку холст ставит переменную на узел строкой входа или выхода (Мастерская, 5.3), short_name — начало id нового узла, glyph — знак вида (путь на сетке 16×16), если он у вида есть, objects — виды сквозных объектов, которые показывают знаком этого вида: у события event, у сигнала signal, у переменной var; по этому признаку, а не по имени вида, окно знает, каким знаком показать объект (правило 2.6), cases_name_outputs: true — выходы вида его варианты, в outputs только other, остальные окно берёт из cases узла (Программа: граф и запуски, раздел 8), frame: { body, returns } — у рамки выход, через который пакет входит в тело, и входы, через которые тело возвращается (Программа: граф и запуски, раздел 11), outside_frames: true — узел вида в рамке не лежит (событие, Программа: граф и запуски, раздел 4), names_module: true — узел вида называет модуль (module_id), и его порты, параметры и шаг — из манифеста этого модуля (Программа: граф и запуски, раздел 12); по этому признаку, а не по имени module, узел-модуль узнают и граф, и окно (правило 2.6). Слова вида лежат в его папке под ключами graph.kind.<вид>.name, .description, .input.<порт>.title и .help, .output.<порт>.title и .help (как у модуля, Модули, таблица 2) и .param.<поле>.title, .help, .choice.<значение>; у поля самого узла — .field.<поле>.title, .help, .choice.<значение> |
process.node.candidates |
запрос | node_id, process_id или document |
{ node_id, existing, available, behind_faces, refused } — с чем этот узел можно соединить (Мастерская, 5.2). Строка списка — порт: { direction: "into"\|"out_of", node_id, kind, module_id, their_port, their_type, my_port, my_type, occupied, cold }; строка behind_faces — такая же и с group_id; строка refused — вход другого узла, к которому выход этого не подвести, и почему: { node_id, port, my_port, reason } |
process.objects |
запрос | process_id; необязательный document — граф, который человек правит: тогда объекты называются по нему, а не по записанному документу |
{ objects, renamed } — среди объектов и out:<node_id>/<выход> у каждого выхода каждого узла (node_id, port, у выхода модуля — type по манифесту) и progress:<node_id> у каждого узла модуля, in:<node_id>/<вход> у каждого входа (sources — откуда в него приходят данные: { output, node_id, parts? } каждой связи) и edge:<edge_id> у каждой связи (node_id и port — куда она ведёт, source — откуда) — сквозные объекты программы, из которых собирают пульт (Форматы файлов, раздел 6): объект — { object_id, kind } и, смотря по роду, node_id или variable_id с type; renamed — прежний object_id → новый по записи renamed_objects документа процесса (Форматы файлов, раздел 5). Список не хранится: его каждый раз выводят из узлов и переменных |
process.insert |
запрос | process_id, source_process_id, опционально document |
{ document, nodes, groups, edges, variables, skipped_events } — документ с добавленным графом другого процесса |
process.group.wrap |
запрос | process_id, node_ids и (или) group_ids одного уровня, опционально name, document |
{ process_id, group_id, document } — документ с новой группой вокруг названного (Программа: граф и запуски, 17.1) |
process.group.unwrap |
запрос | process_id, group_id, опционально document |
{ process_id, document } — документ без названной группы: то, что в ней лежало, лежит там, где лежала она |
process.group.move |
запрос | process_id, node_ids и (или) group_ids, опционально group_id — куда, без него — вон из всех групп, на уровень их места, — document |
{ process_id, document } — документ, где названное лежит в группе group_id; лица следуют за рёбрами (Программа: граф и запуски, раздел 17) |
process.group.face |
запрос | process_id, group_id — или, для отметки заранее на том, что лежит на верхнем уровне, без него: of_group — группа, чья строка лица или переменная отмечается, — иначе node_id — узел, чей порт или переменная, —, что отмечают — variable или node_id, port и side (in | out), — shown (по умолчанию true), опционально name, document |
{ process_id, group_id, document } — документ с изменённым лицом группы; порт, через который ребро пересекает границу, с лица не снимается — graph_face_port_used |
process.node.show |
команда | process_id, node_id, shown — имена параметров модуля или пути полей вида, которые узел показывает на холсте |
{ process_id, node_id, shown }; поле shown_parameters узла записано в сохранённый документ сразу, и у включённой программы: прогон его не читает, поколение не меняется. Узла нет в сохранённом документе — graph_invalid |
process.group.detach |
команда | process_id, group_id, опционально name, document |
{ process_id, node_id, made, variables, document } — группа стала процессом made: { process_id, name }, который уже записан; document — документ с узлом-вызовом node_id на месте группы, variables — переменные, ушедшие в новый процесс (Программа: граф и запуски, 17.1) |
process.variable.rename |
запрос | process_id, variable_id, name, опционально document |
{ process_id, variable_id, nodes, groups, callers, document } — документ с переименованной переменной; nodes и groups — узлы и группы, которые её называли; callers — { process_id, name, node_id } вызовов в других процессах, дающих значение переменной под прежним именем |
process.node.copy |
запрос | process_id, node_ids и (или) group_ids, опционально document |
{ process_id, nodes, groups, document } — документ с копиями названного рядом с ним; nodes и groups — идентификаторы копий названных узлов и групп, в порядке запроса |
process.runs.list |
запрос | опционально process_id |
{ runs, bytes, keep }; прогон — { run_id, process_id, status, resumable, kept, active, modified_ms, bytes, event_node, error, stopped_by }, новые первыми; bytes — служебная папка прогона вместе с его результатами; event_node — событие, начавшее прогон (null у проверки части), error — причина отказа из последней контрольной точки, stopped_by — кто остановил прогон с итогом cancelled |
process.runs.keep |
команда | run_id, keep (по умолчанию true) |
{ run_id, kept }; неизвестный прогон — run_not_found |
process.runs.clear |
команда | опционально process_id |
{ removed, bytes, failed } — удалённые прогоны, освобождённое место и прогоны, которые удалить не удалось, каждый с run_id, error_code и message; отмеченные и идущий остаются |
process.program.on |
команда | process_id |
{ process_id, on: true } — программа включена (Программа: граф и запуски, раздел 2) |
process.program.off |
команда | process_id |
{ process_id, on: false, runs_stopped } — прогоны остановлены, модули выгружены |
process.program.list |
запрос | — | { programs }; программа — { process_id, on, runs_active }, по одной на каждый процесс экземпляра |
process.event.fire |
команда | process_id, node_id события с source: pult (без него — единственное событие пульта), data — данные элемента пульта; by_hand: true — проверочный запуск с холста события любого источника: без data событие получает то, что дал бы источник, — время у часов, {} у остальных (Форматы файлов, 16.6) |
{ run_id, attempt_id, trace_id }, { queued: true, waiting } или { dropped: true } по правилу наложения (Программа: граф и запуски, раздел 5) |
process.event.cancel |
команда | process_id, опционально node_id события |
{ cancelled } — остановленные прогоны этого события, без node_id — все прогоны программы |
process.variable.list |
запрос | process_id, variable_id — необязательно |
{ process_id, values } — текущие значения переменных включённой программы, у выключенной — начальные и запомненные; с variable_id — значение одной этой переменной: так окно читает значение, которое не поместилось в событие |
process.variable.set |
команда | process_id, variable_id, value |
{ variable_id, value }; значение ставится на место прежнего; значение не своего типа или неизвестная переменная — graph_invalid, выключенная программа — program_off |
process.variable.reset |
команда | process_id, variable_ids — непустой список |
{ process_id, values } — каждая названная переменная получила начальное значение, как от узла «Запись» с mode: reset, и события изменения слышат это как запись человека; все имена проверяются до первой записи: неизвестная переменная или переменная прогона — graph_invalid, и ни одна не меняется; выключенная программа — program_off |
process.show.get |
запрос | process_id; node_id — показанное одним узлом |
{ process_id, values, outputs } — что показали узлы «Показать» включённой программы: { <node_id>: { value, cut, run_id, missing? } }; outputs — что последним дал каждый выход каждого узла, { "<node_id>/<выход>": { value, cut, run_id, at } }, урезанное так же; at — порядок, в котором выходы давали значения: из связей одного входа последней пришла та, у источника которой at больше (с node_id — выходы этого узла); после выходов шага окно слышит show.changed с outputs: true; cut — показано не всё, missing — поле, которого не было в пакете; у выключенной программы пусто (Форматы файлов, 16.5) |
process.file.put |
команда | process_id, name, media_type, upload_id, offset, last, data (base64), place = files | run и run_id |
часть файла, который человек даёт процессу: { upload_id, received }, а у последней части — ссылка { file, name, media_type, size }. Части идут по порядку, каждая с байта, на котором кончилась прежняя; отказ — file_invalid (Форматы файлов, 16.4) |
process.file.take |
команда | process_id, path — полный путь Linux к файлу, place = files | run и run_id |
{ file, name, media_type, size } — ссылка на копию файла, лежащего по пути: функция копирует его целиком рядом с местом и затем на место, под своим именем; не файл, нет файла, больше предела graph.file_max_bytes — отказ; нет места на диске — отказ ядра (space_budget_exceeded, space_disk_full) |
process.file.read |
запрос | process_id, file — поле ссылки; у файла запуска run_id; offset, length |
{ data, offset, size, last } — кусок файла в base64, не больше 2 МиБ за раз; ссылка на файл, которого нет, — file_not_found |
process.results.list |
запрос | process_id, run_id |
{ run_id, files } — файлы среди результатов прогона по именам, каждый ссылкой { file, name, media_type, size }, которую читают process.file.read с тем же run_id: то, что сделали модули прогона, и то, что дали прогону. media_type — по окончанию имени. Недоданная часть файла в список не входит; у прогона без результатов список пуст |
process.ask.list |
запрос | process_id |
{ process_id, asks } — открытые вопросы включённой программы: { ask_id, run_id, node_id, question, kind, choices, fill } — поля те же, что у события ask.opened (Обмен с ядром, раздел 7) |
process.ask.answer |
команда | run_id, опционально node_id, answer или declined: true |
{ ok, ask_id }; вопрос уже закрыт — ask_closed (Программа: граф и запуски, раздел 9) |
process.signal.send |
команда | process_id, name сигнала или node_id узла, который его ждёт, необязательные key и data. С node_id имя берётся из узла, а без key ключ читается из data по пути key узла, как у узла «Отправить»; узел не signal — graph_invalid |
{ delivered: true, wait_id, run_id, node_id } — разбудили ветку, которая ждёт этого имени с этим ключом дольше всех; { delivered: false, reason: "nobody_waits" } — ждать некому, сигнал не сохраняется (Программа: граф и запуски, раздел 22). Событие signal.sent публикуется в обоих случаях |
process.message.send |
команда | process_id, name сообщения, необязательно data |
{ delivered: true, events } — сообщение начало прогон каждого события программы source: "message" с этим именем или встало в его очередь, по правилу наложения события; { delivered: false, reason } — program_off, no_receiver или dropped (Программа: граф и запуски, раздел 23). Событие message.sent публикуется в обоих случаях |
process.run.start |
команда | command_id, process_id, target=group\|node, target_id, events |
{ run_id, attempt_id, trace_id } — проверочный запуск части графа включённой программы (Программа: граф и запуски, 12.1); группа запускается со всем, что внутри неё, включая вложенные группы и тела её рамок (17.1 там же). events — набранное в полях событий: { <id события>: пакет }, null — то, что дал бы источник. Что получает вход части снаружи и когда запуск отклоняется (graph_no_entry с message_key graph.part.input_cut или graph.part.event_unfit и names) — Форматы файлов, 16.7 |
process.run.status |
запрос | run_id |
{ run_id, status, active, steps } — оба значения на один момент: running бывает только у активного прогона, и прогон активен, пока выгружаются его модули (module.release); неактивный прогон отвечает итогом. Статус прогона, конца которого функция графа не видела, — например прогона прошлого запуска платформы, — берётся из его контрольной точки; прогон без контрольной точки — run_not_found. steps — шаги модулей, которые у активного прогона сейчас ждут очереди к модулю или идут: { node_id, execution_id, status: "queued"\|"running" }; у неактивного — пусто |
process.run.stop |
команда | run_id; для остановки одной группы — target=group и target_id группы, без run_id во всех идущих прогонах |
статус; для группы — { status, run_id, target, target_id }. Группа и группы внутри неё перестают получать шаги, прогон продолжается остальными ветвями |
process.run.retry |
команда | process_id, опционально node_id, confirm_non_idempotent; для узла-модуля — run_id, input |
без node_id — единственное событие пульта, с node_id события — это событие: последний упавший прогон события повторяет шаг, на котором упал, и продолжается тем же run_id с новым attempt_id (Программа: граф и запуски, таблица 5). Упавшего прогона нет — run_not_found. Отказавший шаг повторяется, когда человек велит; шаг с неизвестным исходом — отказ модуля module_non_idempotent_unknown или шаг модуля non_idempotent или unknown, оборванный остановкой либо кончившийся без ответа модуля (module_process_lost, module_timeout), — без confirm_non_idempotent отклоняется тем же кодом. С node_id узла-модуля — как process.node.step |
process.run.resume |
команда | run_id, подтверждение |
новый attempt_id |
process.node.step |
команда | process_id, run_id, node_id |
один шаг |
process.snapshot |
запрос | run_id |
контрольная точка прогона |
process.save проверяет документ так же, как process.validate, и пишет только process.json. Память живёт, пока её называет узел: после записи функция графа просит module.state.clear для памяти, которую не называет ни один узел-модуль сохранённого документа, — своей у удалённого узла или общей, которую больше никто не называет; отказ очистки говорится событием function.work_failed и сохранения не отменяет (Программа: граф и запуски, раздел 13). Пока программа включена, сохранение отклоняется с process_running; другие процессы сохраняются как обычно. Пульт пишет canvas.save, настройки — settings.set / settings.ensure, версии модулей — module.lock.sync.
process.node.candidates ничего не записывает и не даёт рекомендаций: он перечисляет только те пары портов, которые пройдут проверку графа, — типы сходятся (Программа: граф и запуски, раздел 4), в событие ничего не входит, граница рамки не нарушена, кольца вне рамки нет, горячее и холодное не смешаны и такого ребра ещё нет. occupied — на этом входе уже есть источник, cold — ребро будет холодным (Программа: граф и запуски, раздел 6). Точное совпадение типов идёт раньше того, что разрешил json. refused называет для каждого выхода узла входы других узлов, к которым его не подвести, и причину — последнее слово ключа её слов graph.join.<причина> в словаре функции графа: types — типы не сходятся, itself — тот же узел, no_input — в узел связи не входят, drawn — такое ребро есть, frame — граница рамки, ring — кольцо вне рамки, hot_and_cold — горячее и холодное на одном входе.
process.insert ничего не записывает: он возвращает документ с добавленным графом, а сохраняет его мастерская обычным process.save. Без document берётся сохранённый документ процесса, с ним — правка из окна. Что происходит при вставке:
- узлы, рёбра и группы источника копируются с новыми идентификаторами
<source_process_id>-<id>, при совпадении —-2,-3; - ссылки внутри копии переписываются на новые идентификаторы:
group_idиregionузла,node_idsгруппы; - переменные, на которые ссылаются скопированные узлы, копируются с новыми идентификаторами;
- общая память, которую называют скопированные узлы (
memory), получает новое имя: память, названная идентификатором скопированного узла, — его новым идентификатором, остальная —<source_process_id>-<имя>, при совпадении с памятью этого процесса —-2,-3; узлы, делившие память в источнике, делят её и в копии; - узлы
eventне копируются вместе с рёбрами из них: события у программы свои. Их идентификаторы возвращаются вskipped_events, а вставленные узлы остаются без входящих рёбер. Пока человек не подключил их к событию этой программы, проверка не проходит: узел без горячего входа не сработал бы (Программа: граф и запуски, раздел 6); - пульт источника не копируется;
- копия сдвигается правее того, что уже есть на холсте.
process.group.move тоже ничего не записывает: с лиц снимаются строки того, что группа больше не держит, на лица встают порты, через которые рёбра теперь пересекают границы; группа, легшая поперёк границы рамки или в саму себя, — отказ graph_invalid.
process.group.wrap тоже ничего не записывает:
- названные узлы и группы лежат на одном уровне — прямо в одной группе или на верхнем, — иначе
graph_invalid; узел в теле рамки лежит на уровне ближайшей группы, которая его держит, своей или рамки; - новая группа получает идентификатор
node_1,node_2и далее — первый, которого нет ни у группы, ни у узла документа: человек видит его в подсказке шапки и в «Деталях», и собранный узел называется как узел; - она ложится в группу, где лежало названное, на место левого верхнего угла названного, и свёрнута;
- на её лице — каждый порт, через который ребро пересекает её границу, без своих имён, и переменные, которыми пользуются её узлы;
- узлы получают
group_idновой группы, группы — новыйgroup_id_parent; группа того же уровня в теле оборачиваемой рамки уходит в новую группу вместе с рамкой; - документ, в котором группы не проходят проверку (Программа: граф и запуски, 17.1), не возвращается: отказ называет причину.
process.group.detach — единственный из запросов о группе, который пишет: новый процесс создаётся так же, как при process.create, со своим пультом, настройками и замком модулей, а его граф перед записью проходит ту же проверку, что при сохранении; отказ ничего не оставляет. Имя процесса — name, без него — имя группы, без имени — её идентификатор. Документ вызывающего не записывается: он возвращается в правку. Узел-вызов получает идентификатор call_<n>, входы нового процесса — in_<n>, выходы — out_<n>. Когда группа остаётся на месте, отказ — graph_invalid со своим message_key и перечнем names, о чём речь: graph.detach.own (события и результаты этого процесса в группе, names — узлы), graph.detach.variable (переменная, с которой работают и вне группы или которая стоит на пульте, — пульт спрашивают запросом canvas.get; names — переменные), graph.detach.cold (холодные рёбра через границу, names — рёбра), graph.detach.setting (узлы, берущие настройки процесса) и graph.detach.face (на лице нет входа или выхода).
call в ответе process.list — лицо процесса как узла: inputs и outputs — входы и выходы, названные и типизированные, как порты в манифесте модуля ({ name, type }, тип всегда json): по входу на каждое событие с source: "call" и по выходу на каждый узел result, под их именами, без имени — input и success; variables — переменные процесса, { id, type, initial }, которым вызов задаёт значения параметром values. У процесса без входа call равно null. process.node.candidates берёт порты узла-вызова оттуда же, поэтому в списках соединений стоят именованные входы и выходы вызываемого процесса.
process.variable.rename ничего не записывает: переименованный документ возвращается в правку. Имя меняется в объявлении переменной, в параметре variable узла, вид которого называет переменную, в каждом параметре, взятом из переменной, — объекте из одного поля { "variable": "<имя>" } на любой глубине, но не в самих параметрах: параметр модуля по имени variable его собственный, — в списках variables групп и в renamed_objects (Форматы файлов, раздел 5). Имя, которое не идентификатор, и имя другой переменной — graph_invalid с message_key graph.variable.rename.name и graph.variable.rename.taken; своё имя — не переименование и не отказ. Вызовы в других процессах не правятся: это другие документы.
process.group.unwrap, process.group.face и process.node.copy тоже ничего не записывают: каждый возвращает документ в правку, а документ, в котором группы после этого не проходят проверку, не возвращается.
process.group.unwrap убирает группу и оставляет её содержимое: узлы получают group_id группы, в которой лежала разобранная, и попадают в её node_ids, а на верхнем уровне теряют поле; группы, лежавшие в разобранной, получают её group_id_parent. Лица остальных групп не меняются.
process.group.face ставит на лицо группы порт узла, лежащего в ней на любой глубине, или объявленную переменную и снимает их оттуда. Без group_id запрос ставит и снимает отметку заранее на узле или группе верхнего уровня (face_marks узла, marked строки и face_marks группы, Форматы файлов, раздел 5): узел отмечает свои порты и переменные, с которыми работает, группа — строки и переменные своего лица; то, что лежит в группе, отмечают на её лице, и запрос это отклоняет (graph_invalid). Порт, показанный повторно с name, получает это имя строки; пустое name возвращает строке имя порта. Снять с лица порт, через который ребро пересекает границу группы, нельзя — graph_face_port_used: сначала убирают ребро. Порт узла вне группы, порт, которого у узла нет, и необъявленная переменная — graph_invalid.
process.node.copy копирует названные узлы и группы. Группа копируется со всем, что в ней лежит, и с группами внутри; рамка — с телом и с группами, целиком лежащими в её теле. Копия узла получает идентификатор оригинала со следующим свободным номером после _ (paint → paint_2, module_1 → module_2); копия группы — так же, если её идентификатор кончается номером после _ (node_1 → node_2), иначе со следующим свободным номером после - (cut → cut-2). Свободным считается идентификатор, которого нет ни у узла, ни у группы. Копия лежит там же, где оригинал: group_id и region копии называют группу и рамку оригинала или их копии, если скопированы и они. Копируются рёбра, оба конца которых скопированы; если такое ребро пересекает границу нескопированной группы, порт копии встаёт на её лицо рядом с портом оригинала. Лицо копии группы показывает копии того, что показывало лицо оригинала, под теми же именами. Имя общей памяти (memory) копия сохраняет, кроме памяти, названной идентификатором скопированного узла: она называется идентификатором его копии. Копия сдвинута на 48 вправо и вниз.
process.node.candidates относит в behind_faces соединения, которым мешает только лицо группы: типы сходятся и остальные правила соблюдены, но ребро пересекло бы границу группы через порт, которого на её лице нет. group_id называет эту группу — ближайшую к концу ребра. В existing таких соединений нет: там только то, что можно провести сейчас.
process.validate и process.save проверяют и то, из чего получаются сквозные объекты (Программа: граф и запуски, раздел 16): у переменной — идентификатор, тип из набора и начальное значение своего типа, id не повторяются; узлы variable, write и параметры { "variable": … } ссылаются на объявленные переменные; у события — source и overlap из наборов; у рамки — тело и возвраты; горячие и холодные входы не смешаны (Программа: граф и запуски, раздел 6); группы — дерево, каждая лежит в одном месте, её лицо показывает существующее, и рёбра пересекают её границу только через лицо (17.1 там же). Нарушение — graph_invalid с именем узла, переменной или группы.
process.snapshot читает runs/{run_id}/checkpoint.json — это снимок прогона; сам процесс читает process.open.
После каждого прогона функция графа убирает старые прогоны этого процесса по graph.runs_keep (Программа: граф и запуски, 12.6). Ошибка уборки прогон не валит.
process.run.start отвечает сразу { "run_id", "attempt_id", "trace_id" }; выполнение продолжается. run_id — местные дата и время начала прогона, 2026-10-08_07-29-12, и одинаковым у двух прогонов не бывает; прогон лежит в папке своего процесса (Программа: граф и запуски, 12.6). Событие run.changed — { run_id, trace_id, process_id, status } и event_node, если прогон начало событие, — приходит сначала со статусом running, затем с итоговым; итог failed несёт ещё причину: error_code, message и, если прогон остановился на узле, node_id; итог cancelled — stopped_by (Программа: граф и запуски, 12.5). process.run.status читает тот же итог: running, completed, failed, cancelled; для прогона без контрольной точки — run_not_found. Включение программы отклоняется, если modules.lock.json не совпадает с установленными версиями (module_version_mismatch) или граф не проходит проверку (graph_invalid). Прогон начинается только у включённой программы.
process.run.resume отклоняется с run_active, если прогон идёт сейчас: контрольная точка идущего прогона уже разрешает продолжение — она пишется на случай сбоя, — но прогон, начатый с неё рядом с самим собой, выполнил бы шаги дважды. process.run.resume отклоняется с process_changed, если процесс сохранили после контрольной точки: прогон снят с одной версии графа и на другой не продолжается, остаётся новый прогон. При неопределённом результате шага модуля process.run.resume non_idempotent или unknown отвечает module_non_idempotent_unknown, пока в параметрах нет явного подтверждения { "confirm_non_idempotent": true }. У прогона interrupted неопределёнными считаются шаги модулей из queued контрольной точки; их семантика — поле execution_semantics из module.list. Подтверждение не открывает продолжение для completed, для шага, который модуль отклонил без повтора (refused), и других причин запрета — там ответ resume_not_allowed; отказавший шаг повторяет process.run.retry.