Skip to content

Commit f9ecf95

Browse files
docs(skills): objectstack-automation teaches the record-anchored approval lock read, with an eval prompt (#26495)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8VSG6s7uuGB6TBQmif421jd)_ Fixes #26362 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:`skills/objectstack-automation/references/state-machines-and-approvals.md` 新增一小节「Reading the lock」(12 行):什么时候读锁(不在审批上的读者拿不到 request 行)、路由 `GET /api/v1/approvals/records/:object/:recordId/lock` 答 `{ success: true, data: { locked } }`、SDK `client.approvals.getRecordLock(object, recordId)`、只答 `locked`(request 内容仍在对象的 `approvalsVisibleToReaders` 之后)、读不到的记录与不存在的记录同答 `404 RECORD_NOT_FOUND`。同文件两处复述 Send-back 节的文字削成指针。eval 文件加第 10 题。棘轮脚本两行上限按实测调整,裁决原文写在行旁。 - **为什么改**:PR #26361 把锁读落进了服务、路由与 SDK,文档也写了,技能没教。AI 作者给带审批的对象做记录页时,只会从 request 行的 `lock_record` 读锁;不在审批上的读者拿不到 request 行,页面就给他一个服务端会用 `409 RECORD_LOCKED` 拒掉的编辑按钮。 - **风险与回滚**:只改已发布技能包的文字、一个 eval 夹具和棘轮脚本的两行上限,不改运行时;回滚即 revert 本 PR(上限行随之回落)。 - **先看**:① `references/state-machines-and-approvals.md` L222–L235 新节;② `scripts/check-skills-token-ratchet.mjs` 两行上限及注释;③ `evals/flows-triggers-approvals.json` 第 10 题。 - **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs` → 看到 `state-machines-and-approvals.md is 5730 tokens (ceiling 5730; headroom 0)` 与 `flows-triggers-approvals.json is 2427 tokens (ceiling 2427; headroom 0)`,末行 `81 authored bundle file(s) within their ceilings`。 - **需要你做的**:Tier H(`skills/**`),请 approve。 ## What changes Card #26362's 「Who acts」 paragraph, and nothing wider: 1. **`references/state-machines-and-approvals.md`, one section, "Reading the lock"** (after *Recording a decision*, before *Approver Types*; 12 lines of prose, no code block): a page over a record under approval must not offer an edit the server refuses with `409 RECORD_LOCKED`; the request row tells only a participant, or any reader once the object declares `enable.approvalsVisibleToReaders`, so a non-participant is served no row; the lock itself is `GET /api/v1/approvals/records/:object/:recordId/lock`, in the SDK `client.approvals.getRecordLock(object, recordId)`, answered to anyone who can read the record as `{ success: true, data: { locked } }` — `locked` and nothing else, request content behind that switch, the lock hook's own judgement; the record is read as the caller, so one they cannot read answers `404 RECORD_NOT_FOUND`, the same body an id that does not exist gets. Every clause was read from the merged code, not the card: `packages/plugins/plugin-approvals/src/approval-service.ts` `getRecordLock` (the record is `engine.find`-read as the caller; an unreadable row and a missing id answer the same `null`; the judgement is `recordHeldByLockingApproval`, the hook's own; `recordReaderTierOn` reads `enable.approvalsVisibleToReaders`), `record-lock-read.integration.test.ts` (the non-participant reader is told `{ locked: true }` and still served no request row; `_b` and `MISSING` answer alike), `packages/rest/src/approvals-routes.ts` + `rest-approvals-record-lock-route.test.ts` (one `404 RECORD_NOT_FOUND` for every `null`, `{ locked }` alone), `packages/client/src/index.ts` `getRecordLock`, and `content/docs/automation/approvals.mdx` 「Reading the lock」. The deployment-without-the-member `501` is not taught (not authoring guidance). 2. **`evals/flows-triggers-approvals.json`**: entry 10 — an `expense` record page offers Edit to members not on the approval and their save is refused `409 RECORD_LOCKED`; the ideal answer reads the lock (`client.approvals.getRecordLock`, the route) for anyone who can read the record, hides or disables Edit while `locked`, names the request row a non-participant is never served and the `approvalsVisibleToReaders` switch that is not flipped for this, and the `404` for an unreadable record. `must_contain`: `getRecordLock`, `locked`; `must_not_contain`: `lock_record` (the request-row lookup the card names), `approvalsVisibleToReaders: true` (the other wrong fix: exposing request content to every reader). The expected output is written so that it never spells either `must_not_contain` string (checked by script). 3. **Two genuine restatements cut to pointers**, same file, both of the *Send-back for revision* section, which is their surviving home: the Node Config table's `maxRevisions` row (it restated item 3 — default `3`, `0` disables, only with a `revise` edge — now "Send-backs per run before auto-reject (ADR-0044) — see *Send-back for revision* above", −76 bytes) and the Branching section's send-back bullet (it restated the section's three pieces — the `revise` edge into `approval_revise`, the `type: 'back'` resubmit edge — now the pointer alone, −134 bytes). Nothing else in the file restates another passage. 4. **`scripts/check-skills-token-ratchet.mjs`**, the two objectstack-automation rows this PR touches and no other, under the maintainer's ruling on the seat's decision card 「skills 修复卡的教学和 eval 都撞 token 天花板,怎么付?」, tap **A** (2026-10-27 01:21Z), verbatim and untranslated: 「A:先削重复,余额按实测提高」. Step one, duplicates cut first: the two above (−210 bytes); the eval file's nine fixtures share no sentence (0 bytes to cut). Step two, each row follows its measurement (`ceil(utf8 bytes / 4)`): `state-machines-and-approvals.md` 22346 → 22918 bytes (+782 added, −210 cut, +572 net), 5587 → **5730** (headroom was 2 bytes, 0 tokens; +143); `evals/flows-triggers-approvals.json` 8561 → 9705 bytes (+1144), 2141 → **2427** (headroom was 3 bytes, 0 tokens; +286). Both pinned AT the measurement, each comment quoting the tap. Rows measured against `origin/main` `cc0d77b3` (after #26446 landed as `d1a31d0c`), the branch's base. `SKILL.md` is untouched: its pointer line already routes every approvals question to this reference, and line 1 belongs to #25640. `references/_index.md` is generator-owned (`gen:skill-refs`) and unchanged (no schema moved). The section is prose with inline code, so no `os:check` block was added and the `check:skill-examples` count stays as on `main`. **Two readings** (lines, and tokens as the ratchet counts them): | File | Lines before → after | Tokens before → after | |:--|:--|:--| | `references/state-machines-and-approvals.md` | 380 → 392 (+12) | 5587 → 5730 (+143) | | `evals/flows-triggers-approvals.json` (not .md, reported for the raise) | 95 → 105 (+10) | 2141 → 2427 (+286) | | `SKILL.md` | 441 → 441 (0) | 6164 → 6164 (0) | | **Whole package** (`SKILL.md` + `references/**/*.md`, 4 files; the package has no `rules/`) | **1001 → 1013 (+12)** | **14091 → 14234 (+143)** | Removed: the two Send-back restatements only (−210 bytes, −2 lines). `skip-changeset`: the published skills packages carry no `package.json`; nothing is released from any package. ## Why PR #26361 (#26314) landed `IApprovalService.getRecordLock`, the route and the SDK call, and documented them; the owning skill did not teach them, so an AI author building a page over an object with approvals learned the lock only from the request row, which a reader not on the approval is never served — the page then offered an edit the server refused with `409 RECORD_LOCKED`. `Metadata-protocol: none`: the protocol already carries the route; the gap was the teaching. ## Tests - `node scripts/check-skills-token-ratchet.mjs` — exit 0: both touched rows at headroom 0, `81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted`. - `node scripts/check-skills-token-ratchet.mjs --self-test` — exit 0, `79 cases pass`. - `pnpm --filter @objectstack/spec run check:skill-docs`, `check:skill-refs`, `check:skill-examples` — see Verification. - Eval 10 is a fixture (the repo has no eval runner): a script checked every `must_contain` string occurs in its expected output and neither `must_not_contain` string occurs in the prompt or the expected output. - `grep -i -w role` over the added lines: no hit (`check:role-word` green); no control byte in the three files. ## Acceptance notes - `SKILL.md`'s pointer line to this reference names only the state-introspection route; the lock-read route now lives in the reference's new section. Not changed here (the dispatch order allows a `SKILL.md` edit only if the approvals row must point at the section; the pointer already routes approvals questions to the file): observation only. - The PR body's first two lines are the harness-required attribution block (the os-dev exception); `Fixes #26362` follows immediately. ## Verification Gate list derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on the final tree (base `cc0d77b3`, head `0f8b9708`), each run locally with the exit code captured before any pipe; `--ran` reconciliation below. No commit followed the gate runs, so every reading is of `0f8b9708`. **Reconciliation** (`node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack`): exit 0 — `52 derived famil(ies) accounted for — 51 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)`, 0 UNRUN. The one NOT MEASURED is `pnpm check:dual-build-cjs-loads` (exit 3: its prerequisite is a whole-repo build; this diff touches no package source, and CI's `Lint & Repo Gates` runs it — a declared narrowing). The closures for `check:skill-examples` and `check:doc-formula-expressions` (`@objectstack/client...`, `@objectstack/client-react...`, `@objectstack/lint...`, `@objectstack/formula...`) were built under `scripts/pm/os-verify-lock.sh` (slot `issue-26362`, `VERDICT command-exit 0`, 298 s held, 0 s waited); `check:dts-closure` and `check:lean-entry-closure` ran after that build (`38 built package(s) swept - 160/160 declared declaration file(s) present`). `node scripts/check-comment-mask-corpus.mjs` ran in the serial cheap runner and had finished before `check:skill-examples` started. `check:pm-dispatch-gates` ran detached on the final head: `fast tier, 1935 cases pass`, exit 0. Two runs are outside the derivation and recorded anyway: `check:skill-docs` and `check:skill-examples`, the card's acceptance gates (the section is prose, so no marked block was added). <details><summary>Every command with its exit code on 0f8b970 (54 lines: the 52 derived, plus `check:skill-docs` and `check:skill-examples`)</summary> - `node scripts/check-ci-filter-parity.mjs` — exit 0 - `node scripts/check-closing-keyword-parity.mjs` — exit 0 - `node scripts/check-closing-keyword-parity.mjs --self-test` — exit 0 - `node scripts/check-comment-mask-corpus.mjs` — exit 0 - `node scripts/check-declaration-mirrors.mjs` — exit 0 - `node scripts/check-declaration-mirrors.mjs --self-test` — exit 0 - `node scripts/check-doc-route-spelling.mjs --advisory` — exit 0 - `node scripts/check-doc-route-spelling.mjs --self-test` — exit 0 - `node scripts/check-dts-emitted.mjs --self-test` — exit 0 - `node scripts/check-scripts-symbol-anchors.mjs` — exit 0 - `node scripts/check-scripts-symbol-anchors.mjs --self-test` — exit 0 - `node scripts/check-self-test-wired.mjs` — exit 0 - `node scripts/check-self-test-wired.mjs --self-test` — exit 0 - `node scripts/check-self-test-workflow-commands.mjs` — exit 0 - `node scripts/check-self-test-workflow-commands.mjs --self-test` — exit 0 - `node scripts/check-skills-token-ratchet.mjs` — exit 0 - `node scripts/check-skills-token-ratchet.mjs --self-test` — exit 0 - `node scripts/check-whole-set-label-write.mjs` — exit 0 - `node scripts/check-whole-set-label-write.mjs --self-test` — exit 0 - `node scripts/ci/assert-turbo-remote-cache.mjs --self-test` — exit 0 - `node scripts/ci/plan-test-core-shards.mjs --self-test` — exit 0 - `node scripts/ci/prune-turbo-cache.mjs --self-test` — exit 0 - `node scripts/pm/bare-root-worklist.mjs --self-test` — exit 0 - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` — exit 0 - `pnpm --filter @objectstack/spec run check:skill-refs` — exit 0 - `pnpm check:agent-test-spelling` — exit 0 - `pnpm check:bash32-floor` — exit 0 - `pnpm check:cli-command-ids` — exit 0 - `pnpm check:corpus-claim-drift` — exit 0 - `pnpm check:cross-package-test-inputs` — exit 0 - `pnpm check:doc-authoring` — exit 0 - `pnpm check:driver-memory-census` — exit 0 - `pnpm check:dts-closure` — exit 0 - `pnpm check:dual-build-cjs-loads` — NOT MEASURED (exit 3, PREREQUISITE NOT MET: this gate reads built output and some package has no dist/; CI's Lint & Repo Gates runs it) - `pnpm check:entry-guard` — exit 0 - `pnpm check:gitlink-declared` — exit 0 - `pnpm check:lean-entry-closure` — exit 0 - `pnpm check:nul-bytes` — exit 0 - `pnpm check:parse-guard` — exit 0 - `pnpm check:pm-dispatch-gates` — exit 0 - `pnpm check:pm-governed-merges` — exit 0 - `pnpm check:pm-skill-id-lint` — exit 0 - `pnpm check:pnpm-filter-targets` — exit 0 - `pnpm check:pnpm-flag-spellings` — exit 0 - `pnpm check:ratchet-remedy-authority` — exit 0 - `pnpm check:refd-timer-probe` — exit 0 - `pnpm check:role-word` — exit 0 - `pnpm check:skill-compatibility` — exit 0 - `pnpm check:skill-frame-sync` — exit 0 - `pnpm check:skill-identifier-liveness` — exit 0 - `pnpm check:sourcemap-no-sources-content` — exit 0 - `pnpm check:watch-hint-literal` — exit 0 - `pnpm --filter @objectstack/spec run check:skill-docs` — exit 0 (card acceptance gate, outside the derivation) - `pnpm --filter @objectstack/spec run check:skill-examples` — exit 0 (card acceptance gate, outside the derivation) </details> <details><summary>Token ratchet on 0f8b970</summary> - `skills/objectstack-automation/references/state-machines-and-approvals.md is 5730 tokens (ceiling 5730; headroom 0)` - `skills/objectstack-automation/evals/flows-triggers-approvals.json is 2427 tokens (ceiling 2427; headroom 0)` - `81 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted.` - `--self-test`: `79 cases pass.` </details> <details><summary>check:skill-examples on 0f8b970</summary> `265 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them` (this diff adds no `os:check` block: the new section is prose with inline code). </details> ## Human Minimum Each item is one rule from [CONTRIBUTING.md › Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum). - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema change.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` is CI's run (repo-wide scan). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. Nothing publishes (`skip-changeset`); both declaration lines are filled in above. - [x] 6. `packages/spec` not touched. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English (the 维护者速读 block and the quoted ruling are the required exceptions). --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbZpLcVkA9KV78eHZYr)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 659f2d9 commit f9ecf95

3 files changed

Lines changed: 50 additions & 6 deletions

File tree

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,18 @@ export const CEILINGS = new Map([
796796
// ceiling moves +318, pinned AT the measurement. Same maintainer ruling as
797797
// the SKILL.md row above (2026-10-27, verbatim and untranslated):
798798
// 「A:先削重复,余额按实测提高」.
799-
['skills/objectstack-automation/evals/flows-triggers-approvals.json', 2141],
799+
// 2141 -> 2427 (card #26362): one eval prompt for the record-anchored
800+
// approval lock read — a record page that offers an edit to a member who is
801+
// not on the approval, whose save the lock hook then refuses; the ideal
802+
// answer reads the lock (`client.approvals.getRecordLock`) rather than a
803+
// request row the non-participant is never served, written so it never
804+
// spells either `must_not_contain` string. Paid down first: the file's nine
805+
// fixtures share no sentence, nothing to cut (0 bytes). 8561 -> 9705 bytes
806+
// (+1144) -> ceil(9705 / 4) = 2427; headroom was 3 bytes, 0 tokens, so the
807+
// ceiling moves +286, pinned AT the measurement. Same maintainer ruling as
808+
// the SKILL.md row above (2026-10-27, verbatim and untranslated):
809+
// 「A:先削重复,余额按实测提高」.
810+
['skills/objectstack-automation/evals/flows-triggers-approvals.json', 2427],
800811
// 894 -> 1095 (#26179): one regression case for the field permission `when`
801812
// (the contract prompt: lock the legal terms for finance from approval on,
802813
// legal keeps the edit) — expected: `when` on the finance set's entries with
@@ -882,7 +893,18 @@ export const CEILINGS = new Map([
882893
// bullets and were deleted (~73 tokens). Landed 5555 -> 5587: +32 net, 14
883894
// absorbed by headroom, ceiling +18. Same maintainer ruling as the SKILL.md
884895
// row above (2026-10-27, verbatim and untranslated): 「A:先削重复,余额按实测提高」.
885-
['skills/objectstack-automation/references/state-machines-and-approvals.md', 5587],
896+
// 5587 -> 5730 (card #26362): one short section, "Reading the lock" — the
897+
// record-anchored approval lock read every reader may ask (the route, the
898+
// SDK call, `locked` alone with request content behind
899+
// `approvalsVisibleToReaders`, an unreadable record answered as a missing
900+
// one), +782 bytes. Paid down first, two genuine restatements of the
901+
// Send-back section cut to pointers: the config table's `maxRevisions` row
902+
// (-76 bytes) and the Branching section's send-back bullet (-134 bytes).
903+
// 22346 -> 22918 bytes (+572 net) -> ceil(22918 / 4) = 5730; headroom was 2
904+
// bytes, 0 tokens, so the ceiling moves +143, pinned AT the measurement.
905+
// Same maintainer ruling as the SKILL.md row above (2026-10-27, verbatim and
906+
// untranslated): 「A:先削重复,余额按实测提高」.
907+
['skills/objectstack-automation/references/state-machines-and-approvals.md', 5730],
886908
// 1779 -> 1769 (#26297): RE-LOCK at the landed count after a net cut — two
887909
// stale `{ type: 'lookup', reference: 'user' }` fields (an object that does
888910
// not exist) became `type: 'user'`; -10 tokens, the row follows the

‎skills/objectstack-automation/evals/flows-triggers-approvals.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@
9090
"must_contain": ["record_link", "reference", "defaultValue", "outputVariable", "dialect: 'cel'"],
9191
"must_not_contain": ["type: 'record'", "]("]
9292
}
93+
},
94+
{
95+
"id": 10,
96+
"prompt": "The `expense` record page shows an Edit button to every member who can read the record. While an expense is under approval, members who are not on the approval still get the button and their save is refused with `409 RECORD_LOCKED`. Make the page know the record is locked before it offers the edit.",
97+
"expected_output": "Read the lock itself: `client.approvals.getRecordLock('expense', recordId)` (`GET /api/v1/approvals/records/expense/:recordId/lock`) answers `{ locked }` to anyone who can read the record, participant or not — the same judgement the lock hook applies to the write — so the page hides or disables Edit while `locked` is `true`. Not the request row: a non-participant is served none, and request content stays behind the object's `approvalsVisibleToReaders`, which is not switched on for this. A record the caller cannot read answers `404 RECORD_NOT_FOUND`, the same as a missing one.",
98+
"files": [],
99+
"assertions": {
100+
"must_contain": ["getRecordLock", "locked"],
101+
"must_not_contain": ["lock_record", "approvalsVisibleToReaders: true"]
102+
}
93103
}
94104
]
95105
}

‎skills/objectstack-automation/references/state-machines-and-approvals.md‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,20 @@ reject) — enforced before any write, with no elevation bypass, so the run
219219
cannot resume past the node with the key a later `expression` approver reads
220220
still missing.
221221

222+
### Reading the lock
223+
224+
A page over a record under approval must not offer an edit the server then
225+
refuses with `409 RECORD_LOCKED`. The request row tells only a participant,
226+
or any reader once the object declares `enable.approvalsVisibleToReaders`;
227+
everyone else is served no row. Ask the lock itself —
228+
`GET /api/v1/approvals/records/:object/:recordId/lock`, in the SDK
229+
`client.approvals.getRecordLock(object, recordId)` — answered to anyone who
230+
can read the record as `{ success: true, data: { locked } }`: `locked` and
231+
nothing else (request, approvers and history stay behind that switch), the
232+
same judgement the lock hook applies to an edit. The record is read as the
233+
caller: one they cannot read answers `404 RECORD_NOT_FOUND`, the same body
234+
an id that does not exist gets.
235+
222236
### Approver Types
223237

224238
| `type` | Resolves to |
@@ -350,7 +364,7 @@ Object-hook `ctx` is a different vocabulary — see **objectstack-data**
350364
| `excludeSubmitter` | `true` drops the run's submitter (`submitter_id`) from the resolved slate, whatever approver type produced them — segregation of duties; a slate it empties takes the `onEmptyApprovers` route, never a silent pass. `onEmptyApprovers: 'auto_approve'` is **refused** beside it (it would approve exactly the request this exists to stop): use `admin_rescue`, `fail` or `fallback`. Default `false` |
351365
| `decisionOutputs` | Decision outputs a decision may carry (author declares, approvers fill values). Entries are bare keys (free-text input) **or typed declarations** `{ key, label?, type: 'text'\|'user'\|'department'\|'position'\|'team', multiple?, required? }` — a typed entry renders the matching record picker in the decision dialog (`multiple` collects an id array). Accepted outputs resume the run as `<nodeId>.<key>` variables; undeclared keys reject the decision; `decision`/`requestId` reserved |
352366
| `escalation` | Optional per-node SLA — `{ enabled, timeoutHours, action: reassign\|auto_approve\|auto_reject\|notify, escalateTo?, notifySubmitter }`. `timeoutHours` is **calendar (wall-clock) hours** — nights, weekends and holidays count; the platform ships no business-hours calendar. `escalateTo` is a **position machine name** (expanded to its holders via `sys_user_position`, ADR-0090 D3) or a specific user id — never a membership tier. `reassign` without `escalateTo` degrades to notify (linted) |
353-
| `maxRevisions` | ADR-0044 — max **send-backs-for-revision** per run before auto-reject. Default `3`; `0` disables send-back. Only meaningful when the node has a `revise` out-edge |
367+
| `maxRevisions` | Send-backs per run before auto-reject (ADR-0044) — see *Send-back for revision* above |
354368

355369
### Branching, side-effects & rejection
356370

@@ -362,9 +376,7 @@ These are wired on the **graph**, not in node config:
362376
`http`, a `notify` node, …) to the `approve` / `reject` out-edge.
363377
- **Roll back on reject** — route the `reject` edge as a **back-edge** to an
364378
earlier node so the submitter can revise (the old `back_to_previous`).
365-
- **Send back for revision (ADR-0044)** — distinct from a plain reject: a
366-
`revise` out-edge into an **`approval_revise`** window, closed by a
367-
`type: 'back'` resubmit edge. See *Send-back for revision* above.
379+
- **Send back for revision (ADR-0044)** — see *Send-back for revision* above.
368380
- **Hard reject** — route the `reject` edge to an `end` node (the old
369381
`reject_process`).
370382

0 commit comments

Comments
 (0)