Skip to content

feat(studio): switch a read-only package's flow off for this deployment from the Automations pillar - #28420

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-krgxy0
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-krgxy0

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #28148

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:Studio 自动化里打开一个只读软件包(如 HotCRM)的流程时,头部的开关改为「本部署开关」:读写部署的启用账本(_status / toggle),文案为「本部署中已开启 / 本部署中已关闭 / 本部署中已开启,但未在此运行」,与 Setup › 打包自动化同一套三种读法;可写软件包的流程仍是原来的定义开关和删除。Setup 页的开关请求挪进共用模块,两处发同一个请求。Clone 不在本 PR(归 Flow clone door → "new similar": the copy lands in a writable target package, by default the paired <base>.custom, never package-less #28149)。
  • 为什么改:以前只读流程头部的锁定开关读的是定义,旁边的状态读的是账本,会出现「状态: 已停用」旁边写「🔒 已启用」;管理员也无法就地关掉打包流程,只能去 Setup 找(ADR-0146 D4,裁决「28148 暂停解除」)。
  • 风险与回滚:只动前端;服务端仍是唯一裁判,在 group / isolated 姿态下非平台管理员看到禁用开关和一句原因,真拒绝时原样显示服务端的话。新增 engine.studio.auto.ledger.* 文案键(en、zh),属 Clause-② widening。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(ledgerRow、toggleLedger、flowSwitchEl);ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx;ui/packages/app-shell/src/views/setup/packagedFlowLedger.ts。
  • 两分钟验证:showcase 里进 Studio → 只读软件包 → 自动化,打开 showcase_urgent_task_alert,点头部开关;预期文案在「本部署中已开启 / 已关闭」之间切换、网络面板只有一次 POST /api/v1/automation/showcase_urgent_task_alert/toggle,旁边的状态随之一致;可写软件包的流程仍显示「已启用 / 已停用」。
  • 需要你做的:无,席位落地。

What changes

  • The ledger switch on the item (FlowLedgerSwitch.tsx, new). On a read-only package's open flow, when GET /api/v1/automation/_status serves a row for it, the Automations pillar's header switch binds the ADR-0126 §7.2 activation ledger instead of the definition: aria-checked is the row's enabled, and a click sends POST /api/v1/automation/NAME/toggle at once (no draft is written). Its word is activationReading (views/setup/packagedFlows.ts) for the same row, the reading Setup › Packaged automation shows: "On for this deployment", "On for this deployment, not running here" (muted track, the platform's reason as sent, described on the switch and in its title, never parsed), "Off for this deployment". A writable package's flow keeps its definition switch and its Delete, unchanged. Two switches, two words.
  • Posture-aware, refusal verbatim. useFlowLedgerWalledReadOnly (views/setup/packagedFlowLedger.ts) pre-judges the toggle gate (activation-gate.ts#refuseUngrantedActivationWrite) from the two served facts, features.tenancyPosture via useTenancyPosture() and user.isPlatformAdmin, the posture-and-rung half of walledCatalogReadOnly without its catalog-type filter (director note 6526396053; ⛔ not that function, which answers false for a flow). Under group / isolated a non-operator gets the switch disabled with the console's own sentence; after any attempt the server's refusal is shown verbatim in the pillar's refusal strip.
  • One toggle request. requestFlowToggle moved out of PackagedAutomationPage.tsx into packagedFlowLedger.ts; both surfaces call it with their own fallback words. Setup's clone request stays on Setup's page, untouched.
  • The rail keeps unboundCause. flowRailState now keeps the _status row's unboundCause (non-empty string only) beside reason, so activationReading reads it for presence. The rail, the header status and the Problems panel read exactly as before.
  • i18n. A contiguous block of engine.studio.auto.ledger.* keys in the studio namespace (en, zh). This is the widening behind Clause-②: yes (widening).
  • Resumed work. The branch merges the unreviewed WIP claude/project-thread-dhj1ou@1a9e7703 (release 6541889272) and then drops its Clone half per ruling 6542841890: FlowCloneDialog.tsx, the Clone menu entry, the shared clone request and the engine.studio.auto.clone.* keys are gone; FlowItemMoreMenu.tsx and the Delete test are byte-identical to main.

Tests

ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx, 8 cases, the metadata server doubled at the client and the /automation doors doubled at fetch:

  • the switch reads the ledger, not the definition: definition obsolete + ledger on reads on; a flip posts { enabled: false }, re-reads _status, saves no draft, toasts;
  • the measured disagreement (definition active, ledger off) reads "Off for this deployment", one switch only, no read-only hint;
  • an enabled flow with an unboundCause reads "not running here" with the platform's sentence, wired into aria-describedby;
  • group posture, non-operator: disabled, the console's sentence, nothing sent; CONTROLS single and isolated + operator: live;
  • a 403 refusal is shown verbatim and the switch keeps the ledger's reading;
  • CONTROL: a writable package's flow keeps its definition switch and Delete, and its switch never reaches the ledger door.

All readings below are on head b328333c86 (this branch after its one merge of origin/main at 3fb1a24540), the dependency closure rebuilt first (turbo run build --filter='@object-ui/app-shell^...', 64/64).

Tests and type-check
  • pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check (both run tsc --noEmit and tsc -p tsconfig.test.json, so the tests are compiled): lock VERDICT command-exit 0.
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/studio-design/ packages/app-shell/src/views/setup/ packages/i18n/: Test Files 386 passed (386), Tests 3573 passed, 13 skipped (3586).
  • The new file alone, verbose: Tests 8 passed (8).
  • eslint on the seven changed source files: 0 errors; the 22 warnings are all on lines this diff does not touch.
Ablations (each through scripts/ablation-replace.mjs, restored to blob == HEAD, git diff HEAD empty)

Run on the committed head before the main merge (6d9de7534a; the merge touched none of these files). Each mutation landed (anchor 1 to 0) and the ledger test file went red:

  • A1 checked={ledgerRow.enabled} to checked={flowEnabled} (bind the definition): 4 failed (ledger binding, measured disagreement, not running, refusal).
  • A2 reading={activationReading(ledgerRow)} to a two-way on/off: 1 failed (not running here).
  • A3 flowLedgerWalledReadOnly returns false: 1 failed (walled non-operator).
  • A4 the toggle refusal uses the fallback instead of actionErrorDetail: 1 failed (refusal verbatim).
  • A5 the ledger row no longer requires readOnly: 1 failed (writable package CONTROL). The first A5 attempt was a no-op the tool refused (its replacement was a substring of its anchor, so the count did not rise); re-run with a distinct replacement.

Acceptance notes

  • When _status serves no row for the open flow (an older backend, or a flow the engine never registered), there is no ledger flag to bind, so the read-only definition switch stays as it was, with its lock and the read-only package hint. Card item 3 removes that hint where the ledger switch is drawn, which is every case with a row.
  • The PR body's attribution line links this card thread's project thread, as the harness requires; the seat order names the seat thread instead. Noted, not corrected.
  • Branch name is the platform-assigned card-thread branch claude/project-thread-krgxy0, the recorded exception to claude/issue-N-slug (claim 6542901778).

Verification

Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 34 commands, on b328333
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: 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-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

--ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, every line carries its exit code). node scripts/check-sdui-manifest.mjs is in the list (exit 0). The five type-check-program CI steps the derivation names are CI's.

  • Serial: no open PR touches studio-design/**, views/setup/** or the studio i18n namespace (read from each open UI PR's file list against its merge base). git merge-tree --write-tree of this head with origin/main is clean.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgXdweWzW44zdh5YEjY

…sals and the clone offer

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibwzHimUXAoJvtdW6Tv
…herited ledger-switch work)

Claude-Session: https://claude.ai/code/session_01L6bhgXdweWzW44zdh5YEjY
Co-authored-by: Claude <noreply@anthropic.com>
…in the three activation readings

Drop the Clone half (it moves to its own card): FlowCloneDialog, the
Clone menu entry and the shared clone request go; Setup's Packaged
automation page keeps its own clone request and shares only the toggle.
The switch now reads activationReading's three readings, the muted
not-running one carrying the platform's sentence as sent, and the walled
sentence names the switch alone.

Claude-Session: https://claude.ai/code/session_01L6bhgXdweWzW44zdh5YEjY
Co-authored-by: Claude <noreply@anthropic.com>
… request directly

Claude-Session: https://claude.ai/code/session_01L6bhgXdweWzW44zdh5YEjY
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgXdweWzW44zdh5YEjY
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 31, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv

维护者速读

  • 改了什么:在 Studio 自动化里打开只读软件包的流程,头部开关改成「本部署中已开启 / 已关闭」的启用账本开关。它读 _status,点了立刻经 POST /automation/<name>/toggle 生效,和 Setup › 打包自动化是同一个开关、同一个请求。可写包的流程照旧用定义开关(发布后生效)和删除。
  • 为什么改:以前管理员要关一个打包流程,得离开 Studio 去 Setup。而且 Studio 头部那个锁住的开关读的是流程定义,旁边的状态读的是账本,两者会对不上(Studio: switch a read-only package's flow off for this deployment from the Automations pillar — ledger switch on the item, posture-aware, refusal verbatim #28148,N-04)。
  • 风险与回滚:权限仍由服务端裁定。分墙租户下非平台运营方的开关是灰的,并附原因;服务端拒绝时原样显示原文。Clone 按裁决 6542841890 移到 Flow clone door → "new similar": the copy lands in a writable target package, by default the paired <base>.custom, never package-less #28149,这里已去掉。回滚就是 revert。
  • 先看:setup/packagedFlowLedger.ts(共用请求和分墙判定)、studio-design/FlowLedgerSwitch.tsx、StudioDesignSurface.tsx 里的 ledgerRow / toggleLedger。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx,8 个用例应全绿(在 main 上 7 红 1 绿)。
  • 需要你做的:无。不在受管面上,属 Tier S,席位在 CI 全绿后自己落地。

席位意见:ACCEPT。范围和裁决 6542841890 一致,只做条目上的账本开关。分墙判定按卡第 2 条读两条事实,没有调用 walledCatalogReadOnly。Setup 和 Studio 共用一份请求。

席位核对 — ACCEPT · domain:ui#4 · Head-sha b328333c86b259b0feadcf3999f9680da7f9ce7a · 2026-10-31T13:21Z

  1. Shape: 8 files, +622 / −48, all inside the claim's surface (6542901778). The files are studio-design/FlowLedgerSwitch.tsx (new), StudioDesignSurface.tsx, the new test, setup/packagedFlowLedger.ts (new), setup/PackagedAutomationPage.tsx, studio en.ts / zh.ts, and a root changeset (app-shell patch, i18n patch). FlowItemMoreMenu.tsx and its Delete test are byte-identical to main, and no Clone code remains (ruling 6542841890).
  2. Premise and Target measured (seat's own read):
    • Target 1 (switch reads the ledger): ledgerRow is the engine's _status row and is set only for readOnly && packageId !== null with a served row. The switch's checked is ledgerRow.enabled, never the draft status. Its word is activationReading(ledgerRow), the same three readings Setup draws. flowRailState now keeps unboundCause as a non-empty string only, and activationReading reads it for presence (packagedFlows.ts L97). The not-running sentence is ledgerRow.reason, the field Setup renders (PackagedAutomationPage.tsx L673). A writable package's row has no ledgerRow, so its definition switch is the old branch, unchanged.
    • Target 2 (the server decides): flowLedgerWalledReadOnly is postureHasOrgWall(posture) && !isPlatformOperator. It reads the two facts (useTenancyPosture() and user.isPlatformAdmin) and does not call walledCatalogReadOnly, as the card and director note 6526396053 require. An unknown posture fails toward a live control. A refusal reaches setError through actionErrorDetail verbatim, with a fallback only when no message came back. Setup's own requestToggle now delegates to the same requestFlowToggle, so there is one copy. The body and headers are unchanged: JSON_HEADERS already carried Content-Type.
    • Target 3 (hint): wherever the ledger switch is drawn, the read-only hint is gone. With no _status row there is nothing to bind, and the old read-only switch and its hint stay. I accept this as deviation 2 (see 4).
    • Hooks: useFlowLedgerWalledReadOnly() and the new state sit at the top level of AutomationsPillar, with no top-level return between the component's start (L8672) and the call (L9223).
    • Re-read after a flip: a flip the server accepted patches the row's enabled from the server's answer and bumps statusNonce, which re-reads _status.
  3. Gates and check-runs:
    • On this head at this reading: 13 success, 11 skipped, 28 in progress, 0 failed. Landing waits for every check to be green or an expected skip.
    • The thread reported: new test 8/8 (7 red on main, 1 green writable control), 5 ablations red then restored, type-check green after merging main, studio-design + setup + i18n 3573 tests green, dispatch-gates 34 commands exit 0 (0 NOT-MEASURED), check-sdui-manifest 0.
    • check-governed-merges --pr 28420: NOT governed, 670 changed lines.
  4. Accepted deviations:
    • (1) A tenth key, engine.studio.auto.ledger.notRunning, for the card's third reading.
    • (2) Target 3 holds where the ledger switch is drawn; a flow with no _status row keeps main's read-only switch and hint.
    • (3) The PR body's attribution line links the card thread.
    • (4) The inherited WIP commits 811ecf21 / 1a9e7703 carry the earlier card thread's model-free trailer. History is not rewritten.
  5. Out of scope, with destinations: Clone → Flow clone door → "new similar": the copy lands in a writable target package, by default the paired <base>.custom, never package-less #28149 (N-21). Skills-docs: n/a (Metadata-protocol: none).
  6. Landing clause: Tier S. On all-green, the seat lands it by relay pr_ready plus automerge_enable.

Clause-② review of record (TIER_DEFAULT, in-seat)

  • Declared: Clause-②: yes (widening), on the claim and the PR.
  • What widens:
    • @object-ui/i18n (published) gains a contiguous block of ten engine.studio.auto.ledger.* rows in the studio namespace. The namespace ships only en and zh, and both carry all ten. No key is renamed or removed.
    • packagedFlowLedger.ts (requestFlowToggle, flowLedgerWalledReadOnly, useFlowLedgerWalledReadOnly, FlowDoorFallbacks, FlowToggleOutcome) and FlowLedgerSwitch are new exports of @object-ui/app-shell, which is private: true (package.json L4). No published entry exports them.
  • Does any accept set widen? No. The client sends the same POST /automation/<name>/toggle body Setup already sent. The server's posture gate (refuseUngrantedActivationWrite) is untouched and remains the judge. The client's pre-judgement only disables a control and never enables one the server would refuse.
  • Verdict: PASS. This adds translation rows to a published locale pack and moves a request into a private shared module. No accept set in the contract changes.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 31, 2026 13:33
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 31, 2026 13:33
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 86dc686 Oct 31, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-krgxy0 branch October 31, 2026 13:49
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv

Erratum to ACCEPT 6543912972 · 2026-10-31T13:51Z: the studio namespace gained nine engine.studio.auto.ledger.* keys (on, off, notRunning, hint, walled, switchedOn, switchedOff, toggleFailedHttp, toggleFailed) in en and zh, not ten; deviation (1)'s notRunning is the ninth. The Clause-② verdict (PASS) is unchanged.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant