O.G.A.S. English

Методы графа

Методы функции графа: процессы и программы, запуски, переменные, вопросы, группы. Нужен тому, кто зовёт граф из окна, сценария или другой функции.

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 берётся сохранённый документ процесса, с ним — правка из окна. Что происходит при вставке:

process.group.move тоже ничего не записывает: с лиц снимаются строки того, что группа больше не держит, на лица встают порты, через которые рёбра теперь пересекают границы; группа, легшая поперёк границы рамки или в саму себя, — отказ graph_invalid.

process.group.wrap тоже ничего не записывает:

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.