You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 645534d
Browse filesBrowse the repository at this point in the historyBrowse files
fix(service-automation,types,objectql): a switched-off packaged flow is refused with the same plain sentence a switched-off action reads as (#28097)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D95nzVteRYXwo88m23Tvxk2)_
domain:server#4 · os-elon-musk · session_016WV4cyizCEEdpjyVi8Kmw1
Fixes#27966
Clause-②: no
Metadata-protocol: none
Skills-docs: none — no authorable metadata changes; only the text of an
existing refusal moves
## 维护者速读
- 改了什么:管理员在「设置 › 打包自动化」里关掉的打包流程,被调用时仍以 `FLOW_DISABLED`
拒绝,但提示改成一句给人看的话:「This flow is turned off for this deployment. An
administrator can turn it on again under Setup › Packaged automation, or
copy it there under a new name.」
- 为什么改:旧提示把系统表 `sys_metadata_activation` 和 ADR-0126 §7.2 写给终端用户看。分诊裁定复用
#27964 给动作定下的那一句,而不是再写一份。#27964 的句子是写死在 objectql
里的字符串,service-automation 运行时又不依赖 objectql,所以把这句抽成 `@objectstack/types`
里的一个构建器,动作和流程共用(席位已改认领扩面)。
- 风险与回滚:错误码、状态码、拒绝的范围都没变;动作那句逐字不变。按状态停用的流程仍是「Flow '…' is
disabled」,两种原因照旧可区分。表名和 ADR 改记到一条 info 日志。回滚即还原这个 commit。
-
先看:`packages/types/src/packaged-item-turned-off.ts`、`packages/services/service-automation/src/engine.ts`(`describeDisabledFlow`)、`src/flow-activation-ledger.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run
src/flow-activation-ledger.test.ts`,预期 49 个测试全绿。
- 需要你做的:无,席位落地。
## Measured before the change (`origin/main` 2e955dd, scratch test, not
committed)
| door | `code` | `error` |
|:--|:--|:--|
| direct run (`engine.execute`, the run the REST trigger route calls) |
`FLOW_DISABLED` | `Flow 'lone' is disabled — it is switched off for this
installation in the packaged-metadata activation ledger
(sys_metadata_activation, ADR-0126 §7.2). Re-enable the packaged flow to
arm it again, or run a clone of it under a new name.` |
| subflow (a parent calling a switched-off child) | — | `Node 'call'
failed: subflow 'child' failed: Flow 'child' is disabled — it is
switched off … (sys_metadata_activation, ADR-0126 §7.2). …` |
| status-disabled flow (control, out of scope) | `FLOW_DISABLED` | `Flow
'obs' is disabled` |
The REST trigger door forwards `result.error` verbatim as
`error.message` (pinned at
`packages/runtime/src/domains/automation-trigger-route-status.test.ts:285`),
so it reads whatever the engine says. The retry exit in
`dag-traversal.ts` asks the same `describeDisabledFlow` and is
unreachable through `execute()` today (its own comment).
## Why a new builder in `@objectstack/types`
Triage ruled that the flow sentence reuse "the one builder PR #27964
settles, not a second copy". Measured: #27964's sentence is a string
literal inside `ActionActivationProjection.describeDisabled()`
(`packages/objectql/src/action-activation.ts`), and
`@objectstack/objectql` is only a devDependency of
`@objectstack/service-automation`, so the flow engine cannot call it at
runtime. Both packages already depend on `@objectstack/types`, so the
sentence moved there as `describeTurnedOffPackagedItem('action' |
'flow')`. objectql's `describeDisabled()` now returns its action form,
byte-identical to #27964's. The seat amended the claim's file surface
for this.
## What changes
- **`@objectstack/types`:** new `packaged-item-turned-off.ts` with
`describeTurnedOffPackagedItem`, exported from the package entry. The
two kinds share one shape and differ only in the last clause: flow clone
is chartered (ADR-0126 §7.2, `POST /automation/:name/clone`, offered on
the Setup page), so the flow sentence offers a copy; action-clone is not
(§8 item 2), so the action sentence keeps "or you can use another
action".
- **`@objectstack/service-automation`:** the ledger branch of
`describeDisabledFlow` (`subflow-guards.ts`) returns the flow sentence.
The engine's `describeDisabledFlow` writes an `info` line naming the
code, the flow, `sys_metadata_activation` and ADR-0126 §7.2 whenever it
refuses a ledger-disabled flow (the #27964 log-line pattern). The
status-dimension sentence is unchanged.
- **`@objectstack/objectql`:** `describeDisabled()` delegates to the
builder; its existing #27842 pin is untouched and still green.
- **QA checklist:** `automation.packaged-flow-disable-durable` (revision
8) — acceptance[1] and negative[3] now key on the new sentence; the
verdict is unchanged (the message still separates the ledger dimension
from the status one, the code is still `FLOW_DISABLED`).
- **Changeset:** patch for `@objectstack/service-automation`,
`@objectstack/types`, `@objectstack/objectql`.
## Tests
- `flow-activation-ledger.test.ts`: the four entry-path cases now pin
the exact sentence; new cases pin the plain sentence (no table, no ADR)
plus exactly one `FLOW_DISABLED:` info line naming the flow, the table
and the ADR, and the subflow door (`Node 'call' failed: subflow 'child'
failed: ` followed by the sentence, so the child is still named). The
status-disabled case now asserts it never mentions Packaged automation.
- `engine.test.ts`: two toggle cases follow the new wording.
- `packages/types/src/packaged-item-turned-off.test.ts` (new, 4): both
sentences byte for byte, neither names a table, an ADR or a tracker
number.
- `packages/objectql/src/action-activation.test.ts`: new pin that
`describeDisabled()` IS the builder's action sentence.
<details><summary>Ablations through scripts/ablation-replace.mjs (fix
committed first)</summary>
```
ABLATE-1 subflow-guards.ts: ledger branch back to the old sentence
× the four entry-path cases, × plain-sentence + log case, × subflow-door case,
× engine.test "should disable a flow", × "leaves a run that never dispatched WITHOUT a status"
Tests 8 failed | 205 passed (213) ok restored: blob == HEAD
ABLATE-2 engine.ts: log line guard -> if (false)
× [#27966] the ledger refusal reads as plain words for the caller, and the log line keeps the ledger and the ADR
Tests 1 failed | 48 passed (49) ok restored: blob == HEAD
ABLATE-3 objectql action-activation.ts: describeDisabled returns its own literal
× [#27842] the refusal sentence is plain words …, × [#27966] … not a second copy of it
Tests 2 failed | 20 passed (22) ok restored: blob == HEAD
```
</details>
## Verification (head 4ffb37d, `main` merged after #27951 landed)
- vitest: `@objectstack/service-automation` 226 files / 2864 tests,
`@objectstack/objectql` 494 / 9601, `@objectstack/types` 34 / 836 — all
green.
- `pnpm --filter PKG typecheck` (includes `check:test-typecheck`) exit 0
with PKG each of the three packages.
- `dispatch-gates.mjs --commands`: 67 derived, 67 run, all exit 0
(`check:dual-build-cjs-loads` after building the 7 packages it named);
`--ran` reconcile: 0 NOT-MEASURED, 0 UNRUN.
- `pnpm lint` exit 0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cyizCEEdpjyVi8Kmw1
Co-authored-by: Claude <noreply@anthropic.com>
fix: a flow an administrator switched off is refused with a plain sentence, the same one a switched-off action reads as
8
+
9
+
Clause-②: no
10
+
Metadata-protocol: none
11
+
12
+
-**The flow refusal** (`@objectstack/service-automation`). A packaged flow switched off under Setup › Packaged automation is still refused `FLOW_DISABLED`, at every door that runs it: its trigger, a direct run, and a parent flow that calls it as a subflow. The message now reads: "This flow is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name." It no longer names the ledger table or the ADR. The engine writes those, with the code and the flow's name, to an `info` log line each time it refuses one. A flow disabled by its own status (`obsolete` / `invalid`) still reads "Flow '…' is disabled", so the two reasons stay distinguishable.
13
+
-**One sentence for both** (`@objectstack/types`, `@objectstack/objectql`). `describeTurnedOffPackagedItem('action' | 'flow')` builds the sentence. The action refusal (`ACTION_DISABLED`) reads from it too, and its wording is unchanged. Only the last clause differs: a flow can be copied under a new name, and an action cannot, so the action sentence still offers another action instead.
Copy file name to clipboardExpand all lines: docs/qa/platform-checklist/areas/automation.json
+11-5Lines changed: 11 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -1801,7 +1801,7 @@
1801
1801
"title": "Disabling a packaged flow writes a durable sys_metadata_activation row that survives a cold restart with the trigger left unbound; re-enable UPDATES the row and restores firing",
1802
1802
"since": "v17",
1803
1803
"status": "active",
1804
-
"revision": 7,
1804
+
"revision": 8,
1805
1805
"priority": "P0",
1806
1806
"surface": "api",
1807
1807
"personas": [
@@ -1835,9 +1835,9 @@
1835
1835
"evidence": "the row read (the four ledger columns, and the key set showing no tenant column) + the toggle response"
1836
1836
},
1837
1837
{
1838
-
"clause": "the FLOW_DISABLED refusal message names the LEDGER: triggering the disabled flow answers 409 FLOW_DISABLED whose message carries the sys_metadata_activation / activation-ledger phrasing and the two remedies (re-enable, or clone under a new name)",
1838
+
"clause": "the FLOW_DISABLED refusal message says the flow was switched off by the deployment's packaged-automation switch, in plain words: triggering the disabled flow answers 409 FLOW_DISABLED whose message is the switched-off packaged flow sentence — the flow is turned off for this deployment, an administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name — and names no system table and no ADR (the engine's info log line carries those)",
1839
1839
"oracle": "api",
1840
-
"verify": "the step-5 error message contains 'packaged-metadata activation ledger (sys_metadata_activation' (engine.ts describeDisabledFlow). ⛔ The CODE alone is NOT the oracle: ADR-0126 §7.2 deliberately REUSES FLOW_DISABLED for the ledger dimension (no new ADR-0112 entry), and a status-disabled flow answers the same 409 FLOW_DISABLED with the old bare \"Flow '<name>' is disabled\" — the ledger-vs-status distinction rides the MESSAGE, so a verdict keyed on code alone cannot tell this item's dimension from the authoring one",
1840
+
"verify": "the step-5 error message is exactly \"This flow is turned off for this deployment. An administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name.\"(engine.ts describeDisabledFlow, built by describeTurnedOffPackagedItem in @objectstack/types — the one sentence the switched-off action refusal also reads as) and contains neither 'sys_metadata_activation' nor an ADR number. ⛔ The CODE alone is NOT the oracle: ADR-0126 §7.2 deliberately REUSES FLOW_DISABLED for the ledger dimension (no new ADR-0112 entry), and a status-disabled flow answers the same 409 FLOW_DISABLED with the old bare \"Flow '<name>' is disabled\" — the ledger-vs-status distinction rides the MESSAGE, so a verdict keyed on code alone cannot tell this item's dimension from the authoring one",
1841
1841
"evidence": "the full 409 body with the message quoted"
1842
1842
},
1843
1843
{
@@ -1863,15 +1863,15 @@
1863
1863
"a toggle answering 2xx with NO sys_metadata_activation row written is a FAIL on this boot — the engine has a legitimate ledger-less degraded mode, but it WARNS 'IN PROCESS ONLY … will NOT survive a restart' (engine.ts) and the showcase composition attaches the ledger, so silence plus no row means the durable write was skipped where it was promised",
1864
1864
"a restart that re-arms the flow (enabled or bound true, or a run row from the post-restart create) is a FAIL against the whole point of ADR-0126 §7.2 — it is the retired flowEnabled map's behaviour reappearing",
1865
1865
"a re-enable that DELETES the row is a FAIL of the row contract even though firing resumes — absence-means-active makes deletion look equivalent right up until something needs the recorded choice",
1866
-
"a ledger-disabled flow's trigger answered 409 FLOW_DISABLED with the status dimension's bare sentence (\"Flow 'showcase_urgent_task_alert' is disabled\", with no 'packaged-metadata activation ledger (sys_metadata_activation' phrasing) is a FAIL: the refusal then reports the administrator's ledger choice as an authoring status, and the code alone cannot tell the two apart, because ADR-0126 §7.2 shares it between them by design"
1866
+
"a ledger-disabled flow's trigger answered 409 FLOW_DISABLED with the status dimension's bare sentence (\"Flow 'showcase_urgent_task_alert' is disabled\", with no 'turned off for this deployment' sentence and no Setup › Packaged automation remedy is a FAIL: the refusal then reports the administrator's ledger choice as an authoring status, and the code alone cannot tell the two apart, because ADR-0126 §7.2 shares it between them by design"
1867
1867
],
1868
1868
"traps": [
1869
1869
"first-boot-cold-start",
1870
1870
"seed-data-thin"
1871
1871
],
1872
1872
"source": [
1873
1873
"docs/adr/0126-packaged-metadata-customization-model.md §4 (the generic ledger + row shape), §7.2 (the durable row replaces the process-local flowEnabled map; FLOW_DISABLED code reused, distinction rides the message)",
1874
-
"packages/services/service-automation/src/engine.ts#toggleFlow (toggleFlow — durable row FIRST), (describeDisabledFlow — the ledger message), (hydrateFlowActivations), (registerFlow does not re-arm a ledger-disabled flow — restart survival's other half), (flowLedgerDisabled projection)",
1874
+
"packages/services/service-automation/src/engine.ts#toggleFlow (toggleFlow — durable row FIRST), (describeDisabledFlow — the ledger message, built by packages/types/src/packaged-item-turned-off.ts and logged at info with the ledger table and ADR-0126 §7.2), (hydrateFlowActivations), (registerFlow does not re-arm a ledger-disabled flow — restart survival's other half), (flowLedgerDisabled projection)",
1875
1875
"packages/services/service-automation/src/plugin.ts (hydrate AFTER the boot flow pull — the ordering that makes the unbind stick)",
1876
1876
"packages/core/src/utils/metadata-activation-store.ts#setActive (setActive read-then-update, keyed on exactly the index's two columns; organization_id never in the write payload), (no delete on the engine slice)",
1877
1877
"packages/core/src/utils/metadata-activation-store.ts#list — list() is the whole read path, scoped by metadata_type and by nothing else. ⚠️ The org-row skip this line used to cite is RETIRED, not merely unexercised: the module header records that the NULL-organization filter and the org-row skip both went with the column (#15024)",
@@ -1924,6 +1924,12 @@
1924
1924
"date": "2026-10-27",
1925
1925
"change": "steps[1], steps[7] and steps[9] name the task's required title. Checklist-accuracy finding of the 18.2.0 wave 2 (QA run #26481, priority:P0, at 756220c4; wave anchor #25807, slice A): the task POST needs `title` as well as `project`, and showcase_task has no `name` field (a body using it answers 400 INVALID_FIELD). Re-read at base e4ba846f (task.object.ts: title Field.text required). No clause changed",
1926
1926
"ref": "#25807"
1927
+
},
1928
+
{
1929
+
"revision": 8,
1930
+
"date": "2026-10-30",
1931
+
"change": "acceptance[1] and negative[3] respelled for #27966: the ledger-disabled FLOW_DISABLED message is now written for the caller (the flow is turned off for this deployment, an administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name) and names no system table and no ADR; the engine writes the code, the flow, sys_metadata_activation and ADR-0126 §7.2 to an info log line. It is the same builder the switched-off action sentence reads from (#27842). The verdict is unchanged: the message still distinguishes the ledger dimension from the status one, and the code is still FLOW_DISABLED.",
0 commit comments