Skip to content

Commit c347d22

Browse files
fix(runtime): every door that starts a flow by name refuses a record key the caller cannot read before the run exists (#29227)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9Wc1DX7AbpEoxwZbBojqqdW)_ Fixes #29050 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:所有按名字启动 flow 的入口(两种写法的 trigger 路由、声明式 `type: 'flow'` endpoint、flow 类 action 的 REST 与 MCP 入口)在启动前,都用调用者自己的权限读一次这次启动交给运行的记录;读不到就回 action 入口同一个 `404 RECORD_NOT_FOUND`,不产生运行记录。 - 为什么改:三个入口对「这次启动指向的记录,调用者能不能读」问法不一致,action 入口问了,其余入口没问;以 `runAs: 'system'` 运行的 flow 会越过调用者自己的读权限。本 PR 让三个入口问同一个问题、给同一个答复。 - 风险与回滚:调用者能读的记录、不指定记录的启动、系统身份(进程内调用方)都不变。新增三种拒绝:读不到或不存在的记录回 404;以系统身份运行的 flow 指定了记录但没指定对象回 400;记录键不是单个 id 回 400。用户身份 flow 在指定了对象时也会检查,与 action 入口一致。回滚即撤掉本 PR。 - 先看:`packages/runtime/src/action-execution.ts` 的 `refuseUnreadableFlowStartRecords` 和 `flowActionCallerRecordKeys`;`domains/automation.ts` 的 `respondToFlowTrigger` 与 `flowStartRecordKeys`;`endpoint-executor.ts` 的 `executeFlow`。 - 两分钟验证:`pnpm --filter @objectstack/runtime exec vitest run src/flow-start-record-admission.test.ts`,应看到 32 条全过。 - 需要你做的:无,席位落地。 ## What changes Every door that starts a flow by name now asks a third admission question after `flowStartRefusal` and before `execute()`: **the row this start addresses.** One function, `refuseUnreadableFlowStartRecords` (`packages/runtime/src/action-execution.ts`, beside the action door's subject load that it reuses), is called by: - the trigger routes, `POST /api/v1/automation/:name/trigger` and the legacy `POST /api/v1/automation/trigger/:name` (`respondToFlowTrigger`, `domains/automation.ts`); - a declared endpoint of `type: 'flow'` (`executeFlow`, `endpoint-executor.ts`); - an action of `type: 'flow'` at the REST action route and the MCP `run_action` tool (`dispatchFlowAction`), for the row slots the caller's own `params` fill. The routed record keeps the subject load and refusal it already had, unchanged. The rule it applies: - **Which keys.** Every record key in a row slot the door itself defines (`recordId`, the `{objectName}Id` alias, an action's declared `recordIdParam`), with the value the run will receive, a caller's own `params` value included. - **The load.** Each key is loaded in the caller's own scope through the door's own caller-scope read (`callData('get', …)`, the read the action door's subject load makes), via `loadActionSubjectRecord`. - **The refusal.** A key that scope does not deliver is refused by `refuseDeniedSubjectLoad`: the action door's own `404 RECORD_NOT_FOUND` (ADR-0112 envelope). An unreadable record and an id that names nothing get one answer. Nothing runs and no run is persisted. - **The object.** The object the start names: `objectName` (or `object`) in a trigger or endpoint body, the action's own object at the action door. A start that names a record key but no object cannot be judged: a flow declared `runAs: 'system'` refuses it `400 VALIDATION_FAILED` naming `objectName`; a flow that runs as the user starts as before. - **Non-id keys.** A record key whose value is not one id (a string or a number) is refused `400 VALIDATION_FAILED` naming the key, and is never loaded. - **Unchanged.** A start whose caller can read the record; a start that names no record; the system principal; an action whose `recordIdField` names a field other than `id` (its row slots carry that field's value, not an id). Supporting changes: - `flow-start-admission.ts`: the module docblock states the third question, and `flowRunsElevated` is the one reading of `runAs` it needs. The module still imports nothing from `action-execution.ts`. - `domains/automation.ts`: `recordIdAlias` is now the one spelling of the `{objectName}Id` alias, read by `buildAutomationContext` to seed it and by the new `flowStartRecordKeys` to judge it. The trigger routes answer the refusal through `deps.errorFromThrown`, the exit the action door serves the same refusal through, so both doors answer one envelope. - `content/docs/permissions/system-context.mdx`: row 57 (the flow start admission bypass of the system principal) now names this check and its symbol, and the site register is regenerated with `check-system-context-census.mjs --write`. The new function reads `isSystem` once, so `check:system-context-census` required a row to own it. - Changeset: `.changeset/29050-flow-start-record-admission.md`, a `patch` for `@objectstack/runtime`, rule-only. ## Why #29050: the doors that start a flow by name did not all ask whether the caller can read the record the start hands the run, so the three doors disagreed. `flow-start-admission.ts` already says a door left out of a start rule is the defect. This PR states the rule once and has every door ask it. ## Tests - **New** `packages/runtime/src/flow-start-record-admission.test.ts` (32 tests), over a row-scoped data double: - at both trigger spellings, a flow endpoint, the REST action route and the MCP action tool, a start door given a record key the caller cannot read is refused `404 RECORD_NOT_FOUND`, and no run is created; - a record that does not exist gets the identical answer; - a start on a record the caller can read starts the run (the firing control at each door); - each row slot is judged with the value the run receives; - an elevated start that names a record but no object is refused 400 naming `objectName`, and nothing is loaded; - a record key that is not one id is refused 400 naming the key, and nothing is loaded; - unchanged cases: no record key, the system principal, a user-run flow with no object, an action whose row slots carry a non-id field; - the trigger route and the action door answer the same status, code and message for the same refusal. - **Forced test edits** (8 files). Each fixture started a flow on a record against a data double that did not serve that record to the caller. Each now serves it; no assertion changed: - `flow-run-locale.test.ts`, `flow-run-posture.test.ts`, `flow-caller-param-keys.test.ts`: the trigger-route cases' kernel also serves a data read for the launched record. - `domains/automation-trigger-params-shape.test.ts`: the dispatcher double also serves the launched record (the `a record launch` case). - `domain-handler-registry.test.ts`: the both-routes identity case serves its launched record. - `endpoint-executor.test.ts`: the flow delegation case's `callData` double returns the launched record. - `dispatcher-plugin.endpoint-fallback.integration.test.ts`: the fake engine serves the row the flow endpoint is launched on. - `http-dispatcher.actions-type-dispatch.test.ts`: in `lets an explicit param win over every seeded key`, the caller can also read the record its own param names. ## Acceptance notes - **Doors measured** (the card's assumption A). Four entrances reach `IAutomationService.execute` by flow name from a caller: the two trigger spellings, a declared `type: 'flow'` endpoint, and `dispatchFlowAction` (shared by the REST action route and the MCP `run_action` tool). All four now ask the question. A parent flow's `subflow` node starts its child through the engine, not a door, as `flow-start-admission.ts` already notes. - **The object** (assumption B). The trigger and endpoint bodies carry `objectName`/`object`, and the action door has its action's object. A record key with no object is refused for an elevated flow and passes for a user flow, as described above. No `packages/spec` change. - **User-run flows** (assumption C). The check also runs for user flows when the start names an object, the same way the action door's routed-record load already runs for every flow. Measured cost: 12 existing tests in 8 files had to serve their launched record (the forced edits above). - **Before any run.** Every refusal is asserted with `execute` never called. ## Verification Head `2020b6e5`. Code readings were taken on `5fda11a5` (the merge of `origin/main` at `4550091a`); `2020b6e5` adds only the docs row above. Heavy runs went through `os-verify-lock.sh`. <details><summary>Runtime package: typecheck, full test, lint</summary> - `pnpm --workspace-concurrency=2 --filter '@objectstack/runtime^...' build` after the merge: VERDICT command-exit 0. - `pnpm --filter @objectstack/runtime typecheck` (includes `check:test-typecheck`): VERDICT command-exit 0. - `pnpm --filter @objectstack/runtime test`: Test Files 515 passed (515), Tests 8350 passed, 19 skipped (8369). VERDICT command-exit 0. - Before the fixture edits, the same full run read Test Files 8 failed, 507 passed (515); Tests 12 failed, 8338 passed. All 12 were the record launches named under *Forced test edits*, failing with `expected 404 to be 200`, an `execute` mock called 0 times, or (the action door case) a run that never started. - `vitest run` over the 8 edited files plus the new file: Test Files 9 passed (9), Tests 250 passed (250). - `eslint packages/runtime --no-inline-config` (the root `pnpm lint` command, scoped): exit 0, no output. </details> <details><summary>Ablations: every new refusal goes red when the check is taken out</summary> Each leg used `scripts/ablation-replace.mjs` on one anchor, ran `vitest run src/flow-start-record-admission.test.ts` through the lock, and restored. `git diff HEAD` was 0 bytes after every leg. - **Trigger routes** (the trigger door hands the check no keys): 17 failed, 15 passed. For example, `expected 200 to be 404`, `expected 200 to be 400`, and the parity case `expected { status: 200, … } to deeply equal { status: 404, … }`. - **Flow endpoint** (no keys): 1 failed (`a record key the caller cannot read: 404 RECORD_NOT_FOUND, no run`): `expected 200 to be 404`. - **Action door** (no caller-param keys): 3 failed (REST ×2, MCP): `expected 200 to be 404`, and `promise resolved "{ ok: true, … }" instead of rejecting`. - **No object on an elevated flow** (always start): 2 failed: `expected 200 to be 400`. - **Non-id key** (loaded instead of refused): 2 failed: `expected 404 to be 400`. - **The `{object}Id` slot** (dropped from the keys): 2 failed: `expected 200 to be 404`. </details> <details><summary>dispatch-gates</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on `2020b6e5`: 93 commands. Each ran with stdin closed after a full `turbo run build --filter='./packages/**'` (Tasks 78 successful, 78 total; VERDICT command-exit 0). All 93 exited 0, `check:nul-bytes` included. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE`: "93 derived famil(ies) accounted for — 93 run, 0 NOT-MEASURED (a DERIVED zero — all 93 recorded an exit code and none of them is 3)". Exit 0. - On the first pass (head `5fda11a5`), `check:system-context-census` was red: the new function reads `isSystem` and no row on `content/docs/permissions/system-context.mdx` owned it. Row 57 now does (commit `2020b6e5`). Two prerequisite exits on that pass were cleared and pass on the final run: the teardown-shape self-test's pinned fixture commit was fetched, and the CJS-load gate ran after the full build. - Not measured locally, as `--commands` lists them: the families whose argv takes a value only a CI run has (`check-issue-citations.mjs --census`, the shard attestation emitters, `check-test-completeness.mjs`), and the CI-scheduled jobs (Build Core, Build Docs, the type-check lanes, and the `main`-only dogfood and temporal batteries). Those are CI's readings. </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), which links the AGENTS.md text that states it. - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (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. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmT7iCiGQkgXGQAo4Fo)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c2a5be0 commit c347d22

16 files changed

Lines changed: 755 additions & 20 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): every door that starts a flow by name loads the record it hands the run in the caller's own scope, and refuses a record the caller cannot read before the run exists (#29050)
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
**The rule.** A start that names a record hands the run that record as the row it addresses. Every door that starts a flow by name now loads each such record key in the caller's own scope first, the load the action door already makes for its routed record, and refuses a key that scope does not deliver with the action door's own `404 RECORD_NOT_FOUND` (ADR-0112 envelope). A record the caller cannot read and an id that names nothing get the same answer. Nothing runs: no run record, no node, no side effect.
11+
12+
**Which doors.** `POST /api/v1/automation/:name/trigger` and the legacy `POST /api/v1/automation/trigger/:name`; a declared endpoint of `type: 'flow'`; and an action of `type: 'flow'` (the REST action route and the MCP `run_action` tool), where a record key the caller's own `params` supply is now judged as the routed record already was.
13+
14+
**Which keys.** The row slots the door itself fills: `recordId`, the `<objectName>Id` alias (`crm_lead` → `crmLeadId`) and an action's declared `recordIdParam`, each with the value the run receives, a caller's own `params` value included. A record key names one record by its id; any other value is refused `400 VALIDATION_FAILED` naming the key.
15+
16+
**The object.** The object the start names: `objectName` (or `object`) in a trigger or endpoint request body, and the action's own object at the action door. A start that names a record key but no object cannot be checked: a flow declared `runAs: 'system'` refuses it `400 VALIDATION_FAILED` naming `objectName`, and a flow that runs as the user starts as before.
17+
18+
**What stays as it was.**
19+
20+
- A start whose caller can read the record.
21+
- A start that names no record.
22+
- The system principal (an in-process caller, such as a job).
23+
- The action door's routed record: its load, its refusal and the record-less and new-record actions are unchanged.
24+
- An action whose `recordIdField` names a field other than `id`: its row slots carry that field's value, not a record id, and are not loaded.
25+
26+
**If a start now answers 404 or 400.** Start it on a record the caller can read, and name the record's object (`objectName`) beside `recordId`.

‎content/docs/automation/flows.mdx‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2473,22 +2473,23 @@ curl -b cookies.txt -X POST \
24732473
| `POST /api/v1/automation/:name/runs/:runId/restore-suspension` | Re-arm a run stranded by a failed resume — and, for a nested run, every ancestor whose pause that strand's cascade consumed, deepest first, from whichever member you name. Same gate as `cancel`; body `{ reason? }`. `501 NOT_IMPLEMENTED` when the service does not declare the verb |
24742474

24752475
**Failures answer real HTTP status codes.** A flow that does not run is not a
2476-
`200`. The route classifies seven outcomes. Five of them it reads off the engine's
2477-
own verdict — never the message text; the two `403`s are the door's own admission
2478-
check, made before anything is dispatched:
2476+
`200`. The route classifies eight outcomes. Five of them it reads off the engine's
2477+
own verdict — never the message text; the two `403`s and the record `404` are the
2478+
door's own admission check, made before anything is dispatched:
24792479

24802480
| Status | `error.code` | What happened | What to do |
24812481
|:---|:---|:---|:---|
24822482
| `404` | — | No flow by that name in this deployment | Check the name; nothing was dispatched |
24832483
| `403` | `PERMISSION_DENIED` | The caller is not the system principal, and the flow is declared `runAs: 'system'` with a self-triggered `type` (`autolaunched`, `record_change`, `schedule`). The same answer at every door that starts a flow by name: this route, a `type: 'flow'` action (REST and MCP `run_action`) and a declared `type: 'flow'` endpoint | Let it run on its own trigger, or call it from a parent flow's `subflow` node. If members are meant to start it, declare it `type: 'screen'` (or `'api'` for a signed inbound hook) |
24842484
| `403` | `PERMISSION_DENIED` | The flow declares `requiredPermissions` and the caller, not the system principal, does not hold every capability it names. Checked after the row above, at the same doors; the message names the declared and the missing capabilities | Grant the capability through a permission set's `systemPermissions`, or start the flow as someone who holds it |
2485+
| `404` | `RECORD_NOT_FOUND` | The start names a record (`recordId`, or the object's id alias such as `crmLeadId`) that the caller, not the system principal, cannot read in its own scope, or that does not exist: the two get one answer, the one the action door gives. Checked after the two rows above, at the same doors. A record key that is not one id, or a record named with no `objectName` for a flow declared `runAs: 'system'`, is refused `400` `VALIDATION_FAILED` naming the key | Start the flow on a record the caller can read, and name the record's object (`objectName`) beside `recordId` |
24852486
| `409` | `FLOW_DISABLED` | The flow exists but is switched off | Enable it — the identical request then succeeds |
24862487
| `422` | `FLOW_NO_START_NODE` | The stored definition has no `start` node | Fix the flow; retrying cannot help |
24872488
| `422` | `FLOW_INPUT_SCHEMA_INVALID` | A node's `config` violates the `inputSchema` its own definition declares | Fix the flow; the refusal is non-retryable — even under `errorHandling.strategy: 'retry'` the engine refuses once and never enters the retry loop |
24882489
| `400` | `FLOW_FAILED` | The flow **ran** and was rejected | Read `error.details.summary` for the failing node |
24892490

2490-
The first six never dispatched anything: no node executed, no record was
2491-
written, and there is no run to look up. Only `400` describes a run. The
2491+
The first seven never dispatched anything: no node executed, no record was
2492+
written, and there is no run to look up. Only `400` `FLOW_FAILED` describes a run. The
24922493
`FLOW_INPUT_SCHEMA_INVALID` verdict is a pure function of the flow definition —
24932494
re-running the guard cannot change the answer — so a `retry` strategy does not
24942495
burn its budget on it: the run log holds exactly one refusal row.

‎content/docs/permissions/system-context.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ One of the largest consumers of the flag;
202202
| 55 | Package domain capability gates bypassed | runtime | Get: package management and package-inventory reads without the capability | `requireManageMetadata`, `requireReadCapability` |
203203
| 56 | Activation write / authoring refusals do not fire | runtime | Get: activation artifacts writable and authorable without the activation-authoring capability | `refuseUngrantedActivationWrite`, `refuseUngrantedActivationAuthoring` |
204204
| 55b | Walled environment-metadata refusal does not fire | metadata-core, rest, runtime | Get: under `isolated` / `group`, a write of the environment's metadata, of any type, at the `/meta` write doors (save, reset, publish, rollback) and `POST /meta/_migrate-stored` on both transports, at every `/packages` write door (create, the manifest `PATCH`, enable, disable, publish, `publish-drafts`, `discard-drafts`, `revert`, `rollback`, `commits/:commitId/revert`, `duplicate`, `adopt-orphans`, delete), at the `/automation` definition writes (create, update, delete, clone), and at the permission-set clone door. Every other caller below the platform-operator rung is refused `403` there (ADR-0131 D3, D12 items 7–8, D11), because one organization's write would change what every organization resolves. The seeders, the package door and migrations write the environment's metadata this way. Under `single` the refusal is inert for every caller | `walledCatalogWriteVerdict`, `walledCatalogRefusal`, `refuseWalledCatalogPackageWrite`, `refuseWalledFlowDefinitionWrite` |
205-
| 57 | Automation run-state read, flow-authoring write, the paused-run caller gate (screen read and resume), the two operator run-lifecycle writes and the elevated self-triggered start at every door that starts a flow by name all pass | runtime | Get: run state, flow writes, a paused run's screen and its resume with no grant and without being the run's starter — and cancelling or restoring a suspension without the platform-operator rung. The screen read and the resume ask one predicate, so this is one bypass for both. The lifecycle bypass is the in-process owner's door: plugin-approvals' revise-window recall (ADR-0044) cancels on behalf of a decision it already authorized and recorded. And starting a flow declared `runAs: 'system'` whose type is self-triggered (`autolaunched`, `record_change`, `schedule`) through any door that starts a flow by name — the trigger route, an action of `type: 'flow'` (REST `/actions` and the MCP `run_action` bridge) and a declared endpoint of `type: 'flow'` — which every other caller, the guest principal included, is refused `403 PERMISSION_DENIED` (the maintainer's rulings, letters B and A) — and starting, through the same doors, a flow whose `requiredPermissions` the caller does not hold (ruling record 6377848497, letter A). The three doors ask one check, so this is one bypass for all of them, and it is the in-process caller's, such as a job: inbound HTTP never carries the flag | `mayReadRunState`, `refuseUngrantedFlowWrite`, `isRunStarterOrRunStateReader`, `refuseUngrantedRunLifecycleWrite`, `flowStartRefusal` |
205+
| 57 | Automation run-state read, flow-authoring write, the paused-run caller gate (screen read and resume), the two operator run-lifecycle writes, and the elevated self-triggered start and the start-record check at every door that starts a flow by name all pass | runtime | Get: run state, flow writes, a paused run's screen and its resume with no grant and without being the run's starter — and cancelling or restoring a suspension without the platform-operator rung. The screen read and the resume ask one predicate, so this is one bypass for both. The lifecycle bypass is the in-process owner's door: plugin-approvals' revise-window recall (ADR-0044) cancels on behalf of a decision it already authorized and recorded. And starting a flow declared `runAs: 'system'` whose type is self-triggered (`autolaunched`, `record_change`, `schedule`) through any door that starts a flow by name — the trigger route, an action of `type: 'flow'` (REST `/actions` and the MCP `run_action` bridge) and a declared endpoint of `type: 'flow'` — which every other caller, the guest principal included, is refused `403 PERMISSION_DENIED` (the maintainer's rulings, letters B and A) — and starting, through the same doors, a flow whose `requiredPermissions` the caller does not hold (ruling record 6377848497, letter A). And starting, through the same doors, a flow on a record key the caller's own scope does not deliver, which every other caller is refused with the action door's `404 RECORD_NOT_FOUND` before the run exists (and `400 VALIDATION_FAILED` for a key that is not one id, or for an elevated start that names no object). The three doors ask one check, so this is one bypass for all of them, and it is the in-process caller's, such as a job: inbound HTTP never carries the flag | `mayReadRunState`, `refuseUngrantedFlowWrite`, `isRunStarterOrRunStateReader`, `refuseUngrantedRunLifecycleWrite`, `flowStartRefusal`, `refuseUnreadableFlowStartRecords` |
206206
| 57b | Connector credential door's capability and posture gates pass | runtime | Get: listing, setting and clearing the environment's stored connector credentials (`/automation/connectors/credential-refs`) without `manage_metadata`, and under the `group` / `isolated` postures, where the door refuses every other caller `403 PERMISSION_DENIED` naming the posture, the platform operator included, because the store is environment-level. The bypass is the in-process caller's: inbound HTTP never carries the flag. Get nothing more about the value: no answer of this door carries it | `refuseUngrantedConnectorCredentialAccess`, `refuseWalledConnectorCredentialAccess` |
207207
| 57c | Connector connection door's capability gate passes | runtime | Get: reading, beginning, completing (the OAuth callback) and revoking an organization's connection of an `oauth2` `authorization_code` connector instance (`/automation/connectors/:name/connection`, `/automation/connectors/oauth/callback`, ADR-0143 D5) without `manage_metadata`. No posture refuses it: a connection belongs to its organization. The bypass is the in-process caller's: inbound HTTP never carries the flag. Get nothing more: no answer of this door carries a token, and the callback's `state` is still bound to the user and organization that began the connect | `refuseUngrantedConnectorConnectionAccess` |
208208
| 58 | Audience-binding suggestion authority gate passes | plugin-security | Get: a system caller lists, accepts and dismisses suggestions without `manage_metadata` and, under a wall, without the platform-operator rung | `assertDeploymentAuthority` |
@@ -625,6 +625,7 @@ and CI refuses one that differs from what the tree produces.
625625
| `packages/runtime/src/action-execution.ts#actionPermissionError` | 1 | 49 |
626626
| `packages/runtime/src/action-execution.ts#callData` | 1 | 48 |
627627
| `packages/runtime/src/action-execution.ts#refuseUnexposedDataAction` | 1 | 48 |
628+
| `packages/runtime/src/action-execution.ts#refuseUnreadableFlowStartRecords` | 1 | 57 |
628629
| `packages/runtime/src/domains/actions.ts#handleActionsRequest` | 1 | 52 |
629630
| `packages/runtime/src/domains/activation-gate.ts#refuseUngrantedActivationAuthoring` | 1 | 56 |
630631
| `packages/runtime/src/domains/activation-gate.ts#refuseUngrantedActivationWrite` | 1 | 56 |

0 commit comments

Comments
 (0)